Compare commits
8 Commits
feat/creat
...
master
Author | SHA1 | Date | |
---|---|---|---|
e9ce2d1f9f | |||
3cb5a30270 | |||
2cb2166093 | |||
1593fa3493 | |||
57a57c63ff | |||
0c85df498e | |||
e0c3580269 | |||
1bbc7a2aa3 |
@ -1,4 +1,4 @@
|
|||||||
name: Build Docker Image # nom du workflow
|
name: Build Docker Image Front # nom du workflow
|
||||||
|
|
||||||
on: #declancheur
|
on: #declancheur
|
||||||
push:
|
push:
|
53
.github/workflows/build_docker_express.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
name: Build Docker Image Back # nom du workflow
|
||||||
|
|
||||||
|
on: #declancheur
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run: #jobs ID (nom du jobs)
|
||||||
|
runs-on: ubuntu-latest # environement de run
|
||||||
|
|
||||||
|
steps: # liste des étapes
|
||||||
|
- name: Checkout # rapatrie le depot
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
git.lab-ouest.org/Epitech/ratrapage_T-WEB_back
|
||||||
|
tags: |
|
||||||
|
type=edge
|
||||||
|
type=ref,event=pr
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=semver,pattern=latest
|
||||||
|
|
||||||
|
- name: Login to Gitea
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.lab-ouest.org
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
# - name: Set up Docker Buildx
|
||||||
|
# uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push back
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ./Express/barAndCafe
|
||||||
|
push: true
|
||||||
|
file: ./Express/barAndCafe/Dockerfile
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
46
.github/workflows/build_jsdoc_drink.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: JsDocs
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
jobs:
|
||||||
|
coverage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout # rapatrie le depot
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
git.lab-ouest.org/Epitech/ratrapage_T-WEB_drink_jsdocs
|
||||||
|
tags: |
|
||||||
|
type=edge
|
||||||
|
type=ref,event=pr
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=semver,pattern=latest
|
||||||
|
|
||||||
|
- name: Login to Gitea
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.lab-ouest.org
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
# - name: Set up Docker Buildx
|
||||||
|
# uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push docs
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ./Express/barAndCafe
|
||||||
|
push: true
|
||||||
|
file: ./Express/barAndCafe/Dockerfile_docs
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
21
.github/workflows/run_back_test.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Test and coverage
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
jobs:
|
||||||
|
coverage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: ArtiomTr/jest-coverage-report-action@v2
|
||||||
|
with:
|
||||||
|
working-directory: Express/barAndCafe
|
||||||
|
test-script: npm run test
|
||||||
|
output: comment, report-markdown
|
||||||
|
prnumber: ${{ steps.findPr.outputs.number }}
|
||||||
|
custom-title: Coverage report for backend
|
||||||
|
github-token: ${{ secrets.PR_TOCKEN}}
|
||||||
|
annotations: none #disable annotation
|
||||||
|
env:
|
||||||
|
OPEN_TRIP_MAPS_KEY: ${{ secrets.OPEN_TRIP_MAPS_KEY}}
|
59
Express/barAndCafe/Dockerfile
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#########
|
||||||
|
# Build #
|
||||||
|
#########
|
||||||
|
FROM docker.io/node:20-alpine as BUILD_IMAGE
|
||||||
|
|
||||||
|
# External deps (for node-gyp add: "python3 make g++")
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# run as non root user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# go to user repository
|
||||||
|
WORKDIR /home/node
|
||||||
|
|
||||||
|
# Add package json
|
||||||
|
ADD --chown=node:node package.json package-lock.json ./
|
||||||
|
|
||||||
|
# install dependencies from package lock
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Add project files
|
||||||
|
ADD --chown=node:node . .
|
||||||
|
|
||||||
|
# build
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# remove dev deps
|
||||||
|
RUN npm prune --omit=dev
|
||||||
|
|
||||||
|
##############
|
||||||
|
# Production #
|
||||||
|
##############
|
||||||
|
FROM docker.io/node:20-alpine as PROD_IMAGE
|
||||||
|
|
||||||
|
# inform software to be in production
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
ENV RESOURCES_FOLDER=/home/node/.loop/uploads
|
||||||
|
|
||||||
|
# run as non root user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# go to work folder
|
||||||
|
WORKDIR /home/node
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Add Healthcheck
|
||||||
|
# FIXME: faire en sorte que le healthcheck fonctionne
|
||||||
|
# HEALTHCHECK --interval=10s --timeout=10s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||||
|
|
||||||
|
# copy from build image
|
||||||
|
COPY --chown=node:node --from=BUILD_IMAGE /home/node/node_modules ./node_modules
|
||||||
|
COPY --chown=node:node --from=BUILD_IMAGE /home/node/dist ./dist
|
||||||
|
COPY --chown=node:node --from=BUILD_IMAGE /home/node/package.json /home/node/.env* ./
|
||||||
|
|
||||||
|
# run it !
|
||||||
|
CMD ["npm", "run", "start"]
|
35
Express/barAndCafe/Dockerfile_docs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#########
|
||||||
|
# Build #
|
||||||
|
#########
|
||||||
|
FROM docker.io/node:20-alpine as BUILD_IMAGE
|
||||||
|
|
||||||
|
# External deps (for node-gyp add: "python3 make g++")
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# run as non root user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# go to user repository
|
||||||
|
WORKDIR /home/node
|
||||||
|
|
||||||
|
# Add package json
|
||||||
|
ADD --chown=node:node package.json package-lock.json ./
|
||||||
|
|
||||||
|
# install dependencies from package lock
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Add project files
|
||||||
|
ADD --chown=node:node . .
|
||||||
|
|
||||||
|
# build
|
||||||
|
RUN npm run docs
|
||||||
|
|
||||||
|
# remove dev deps
|
||||||
|
RUN npm prune --omit=dev
|
||||||
|
|
||||||
|
##############
|
||||||
|
# Production #
|
||||||
|
##############
|
||||||
|
FROM httpd:alpine
|
||||||
|
|
||||||
|
COPY --from=BUILD_IMAGE /home/node/out/ /usr/local/apache2/htdocs/
|
24
Express/barAndCafe/jest.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
testPathIgnorePatterns: [
|
||||||
|
"<rootDir>/dist/"
|
||||||
|
],
|
||||||
|
testTimeout: 10000,
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
collectCoverage: true,
|
||||||
|
coverageDirectory: 'coverage',
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'**/*.ts',
|
||||||
|
'!/nodemodules/',
|
||||||
|
'!/jest.config.js/',
|
||||||
|
'!/coverage/',
|
||||||
|
'!**/server.ts',
|
||||||
|
'!**/swaggerDef.ts'
|
||||||
|
],
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
lines: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
20
Express/barAndCafe/jsdoc.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"opts": {
|
||||||
|
"template": "node_modules/better-docs"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"allowUnknownTags": true
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"node_modules/better-docs/typescript",
|
||||||
|
"plugins/markdown",
|
||||||
|
"jsdoc-mermaid"
|
||||||
|
],
|
||||||
|
"source": {
|
||||||
|
"include": ["./src"],
|
||||||
|
"includePattern": "\\.(jsx|js|ts|tsx)$"
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"search": true
|
||||||
|
}
|
||||||
|
}
|
12417
Express/barAndCafe/package-lock.json
generated
Normal file
44
Express/barAndCafe/package.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rimraf dist && npx tsc",
|
||||||
|
"start": "node dist/src/server.js",
|
||||||
|
"predev": "npm run build",
|
||||||
|
"dev": "npx tsc -w & nodemon dist/src/server.js",
|
||||||
|
"predocs": "npm run build",
|
||||||
|
"docs": "jsdoc -c jsdoc.json",
|
||||||
|
"pretest": "npm run build",
|
||||||
|
"test": "ts-node -O '{\"module\":\"commonjs\"}' node_modules/jest/bin/jest.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/node": "^20.12.7",
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"swagger-jsdoc": "^6.2.8",
|
||||||
|
"swagger-ui-express": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
|
"@types/rewire": "^2.5.30",
|
||||||
|
"@types/supertest": "^6.0.2",
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"better-docs": "^2.7.3",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jsdoc": "^4.0.3",
|
||||||
|
"jsdoc-mermaid": "^1.0.0",
|
||||||
|
"nodemon": "^3.1.0",
|
||||||
|
"rewire": "^7.0.0",
|
||||||
|
"superagent": "^9.0.2",
|
||||||
|
"supertest": "^7.0.0",
|
||||||
|
"ts-jest": "^29.1.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typedoc": "^0.25.13",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
}
|
||||||
|
}
|
169
Express/barAndCafe/src/app.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import { getCity, getRadius, getPoiId, getBox } from "./openTripMaps"
|
||||||
|
import express from "express"
|
||||||
|
import cors from "cors"
|
||||||
|
/**
|
||||||
|
* Initialize Express application instance.
|
||||||
|
* @returns An initialized Express application object.
|
||||||
|
*/
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.use(cors())
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle GET request for homepage route ('/welcome').
|
||||||
|
* Send back a simple json response.
|
||||||
|
* @param {express.Request} req - HTTP Request object.
|
||||||
|
* @param {express.Response} res - HTTP Response object.
|
||||||
|
*/
|
||||||
|
function getWelcome(req: express.Request, res: express.Response) {
|
||||||
|
const out = {hello:"world"}
|
||||||
|
res.send(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /welcome:
|
||||||
|
* get:
|
||||||
|
* summary: retun just hello
|
||||||
|
* description: Welcome to swagger-jsdoc!
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Returns a welcome json.
|
||||||
|
*/
|
||||||
|
app.get("/welcome", getWelcome)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /otm/city:
|
||||||
|
* get:
|
||||||
|
* summary: return the drinks in a city
|
||||||
|
* description: return the drinks in a defined perimeter in a city
|
||||||
|
* parameters:
|
||||||
|
* - name: name
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: the name of the city
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Return a list of bars and coffee in city in geoJSON format
|
||||||
|
* 400:
|
||||||
|
* description: Missing Argument Error
|
||||||
|
* 401:
|
||||||
|
* description: Missing OTM tocken
|
||||||
|
*/
|
||||||
|
app.get("/otm/city", getCity)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /otm/radius:
|
||||||
|
* get:
|
||||||
|
* summary: return the drinks in a radius
|
||||||
|
* description: return the drinks in a defined radius
|
||||||
|
* parameters:
|
||||||
|
* - name: lon
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: longitude of the center of the radius
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* minimum: -180
|
||||||
|
* maximum: 180
|
||||||
|
* - name: lat
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: latitude of the center of the radius
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* minimum: -90
|
||||||
|
* maximum: 90
|
||||||
|
* - name: radius
|
||||||
|
* in: query
|
||||||
|
* description: size of the radius
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Return a list of bars and coffee in city in geoJSON format
|
||||||
|
* 400:
|
||||||
|
* description: Missing Argument Error
|
||||||
|
* 401:
|
||||||
|
* description: Missing OTM tocken
|
||||||
|
*/
|
||||||
|
app.get("/otm/radius", getRadius)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /otm/poidetaill:
|
||||||
|
* get:
|
||||||
|
* summary: detail of a POI
|
||||||
|
* description: detail of a POI link name, kind, rate,...
|
||||||
|
* parameters:
|
||||||
|
* - name: id
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: Unique identifier of the object in OpenTripMap
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Return the detaill of an POI in otm
|
||||||
|
* 400:
|
||||||
|
* description: Missing Argument Error
|
||||||
|
* 401:
|
||||||
|
* description: Missing OTM tocken
|
||||||
|
*/
|
||||||
|
app.get("/otm/poidetaill", getPoiId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /otm/box:
|
||||||
|
* get:
|
||||||
|
* summary: return the drinks in a box
|
||||||
|
* description: return the drinks in a defined box
|
||||||
|
* parameters:
|
||||||
|
* - name: lon1
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: longitude 1 of the box
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* minimum: -180
|
||||||
|
* maximum: 180
|
||||||
|
* - name: lat1
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: latitude 1 of the box
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* minimum: -90
|
||||||
|
* maximum: 90
|
||||||
|
* - name: lon2
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: longitude 2 of the box
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* minimum: -180
|
||||||
|
* maximum: 180
|
||||||
|
* - name: lat2
|
||||||
|
* in: query
|
||||||
|
* required: true
|
||||||
|
* description: latitude 2 of the box
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* minimum: -90
|
||||||
|
* maximum: 90
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Return a list of bars and coffee in city in geoJSON format
|
||||||
|
* 400:
|
||||||
|
* description: Missing Argument Error
|
||||||
|
* 401:
|
||||||
|
* description: Missing OTM tocken
|
||||||
|
*/
|
||||||
|
app.get("/otm/box", getBox)
|
||||||
|
|
||||||
|
export default app
|
198
Express/barAndCafe/src/openTripMaps.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import express from "express"
|
||||||
|
import * as dotenv from "dotenv"
|
||||||
|
|
||||||
|
dotenv.config({path: '../../.env'})
|
||||||
|
const key = process.env.OPEN_TRIP_MAPS_KEY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make a GET request to the OTM for a rectangle search.
|
||||||
|
* @param {string} lon1 Longitude of the 1st point of the box
|
||||||
|
* @param {string} lat1 Latitude of the 1st point of the box
|
||||||
|
* @param {string} lon2 Longitude of the 2nd point of the box
|
||||||
|
* @param {string} lat2 Latitude of the 2nd point of the box
|
||||||
|
* @returns {FeatureCollection} a list of POIs with their type, id, etc. (cf: [opentripmap](https://dev.opentripmap.org/docs#))
|
||||||
|
*/
|
||||||
|
async function callBox(lon1:string, lat1:string, lon2: string, lat2: string, rate: string) {
|
||||||
|
const lonMin = Math.min(parseFloat(lon1), parseFloat(lon2))
|
||||||
|
const lonMax = Math.max(parseFloat(lon1), parseFloat(lon2))
|
||||||
|
const latMin = Math.min(parseFloat(lat1), parseFloat(lat2))
|
||||||
|
const latMax = Math.max(parseFloat(lat1), parseFloat(lat2))
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.opentripmap.com/0.1/en/places/bbox',
|
||||||
|
params: {
|
||||||
|
lon_min: lonMin,
|
||||||
|
lon_max: lonMax,
|
||||||
|
lat_min: latMin,
|
||||||
|
lat_max: latMax,
|
||||||
|
rate: rate,
|
||||||
|
apikey: key,
|
||||||
|
kinds: 'bars,cafes,pubs,biergartens'
|
||||||
|
},
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.request(options)
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make GET request to the OTM for radius search.
|
||||||
|
* @param {string} lon Longitude of radius center point
|
||||||
|
* @param {string} lat Latitude du point central du rayon
|
||||||
|
* @param {string} radius search radius size in meters
|
||||||
|
* @returns {FeatureCollection} a list of POIs with their type, id, etc. (cf: [opentripmap](https://dev.opentripmap.org/docs#))
|
||||||
|
*/
|
||||||
|
async function callRadius(lon: string, lat: string, radius = '1000') {
|
||||||
|
const optionsDrink = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.opentripmap.com/0.1/en/places/radius',
|
||||||
|
params: {
|
||||||
|
radius: radius,
|
||||||
|
lon: lon,
|
||||||
|
lat: lat,
|
||||||
|
apikey: key,
|
||||||
|
kinds: 'bars,cafes,pubs,biergartens'
|
||||||
|
},
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.request(optionsDrink)
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make GET request to the OTM for city info.
|
||||||
|
* @param {string} name Name of a city
|
||||||
|
* @returns {Geoname} info of a search city their name, country, lat, lon, etc. (cf: [opentripmap](https://dev.opentripmap.org/docs#))
|
||||||
|
*/
|
||||||
|
async function callCity(name:string) {
|
||||||
|
const optionsCity = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.opentripmap.com/0.1/en/places/geoname',
|
||||||
|
params: {
|
||||||
|
name: name,
|
||||||
|
apikey: key
|
||||||
|
},
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.request(optionsCity)
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make GET request to the OTM for city info.
|
||||||
|
* @param {string} name Name of a city
|
||||||
|
* @returns {Geoname} info of a search city their name, country, lat, lon, etc. (cf: [opentripmap](https://dev.opentripmap.org/docs#))
|
||||||
|
*/
|
||||||
|
async function callId(id:string) {
|
||||||
|
const optionsId = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.opentripmap.com/0.1/en/places/xid/' + id,
|
||||||
|
params: {
|
||||||
|
apikey: key
|
||||||
|
},
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.request(optionsId)
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle GET request for city search route ('/otm/city').
|
||||||
|
* @param {express.Request} req - HTTP Request object.
|
||||||
|
* @param {express.Response} res - HTTP Response object.
|
||||||
|
*/
|
||||||
|
export async function getCity(req: express.Request, res: express.Response) {
|
||||||
|
const cityName = req.query["name"]
|
||||||
|
let radius = req.query["radius"]
|
||||||
|
|
||||||
|
if(!cityName){
|
||||||
|
res.status(400).send("Missing Argument name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(!radius){
|
||||||
|
radius = "1000"
|
||||||
|
}
|
||||||
|
const cityPose = await callCity(cityName as string)
|
||||||
|
res.send( await callRadius(cityPose.lon,cityPose.lat, radius as string))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle GET request for radius search route ('/otm/radius').
|
||||||
|
* @param {express.Request} req - HTTP Request object.
|
||||||
|
* @param {express.Response} res - HTTP Response object.
|
||||||
|
*/
|
||||||
|
export async function getRadius(req:express.Request, res: express.Response) {
|
||||||
|
const lon = req.query["lon"] as string
|
||||||
|
const lat = req.query["lat"] as string
|
||||||
|
let radius = req.query["radius"]
|
||||||
|
if(!lon || !lat){
|
||||||
|
res.status(400).send("Missing Argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(!radius){
|
||||||
|
radius = "1000"
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send( await callRadius(lon,lat,radius as string))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle GET request for radius search route ('/otm/radius').
|
||||||
|
* @param {express.Request} req - HTTP Request object.
|
||||||
|
* @param {express.Response} res - HTTP Response object.
|
||||||
|
*/
|
||||||
|
export async function getPoiId(req: express.Request, res: express.Response){
|
||||||
|
const id = req.query["id"] as string
|
||||||
|
if(!id){
|
||||||
|
res.status(400).send("Missing Argument name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.send( await callId( id as string))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle GET request for radius search route ('/otm/box').
|
||||||
|
* @param {express.Request} req - HTTP Request object.
|
||||||
|
* @param {express.Response} res - HTTP Response object.
|
||||||
|
*/
|
||||||
|
export async function getBox(req:express.Request, res: express.Response) {
|
||||||
|
const lon1 = req.query["lon1"] as string
|
||||||
|
const lat1 = req.query["lat1"] as string
|
||||||
|
const lon2 = req.query["lon2"] as string
|
||||||
|
const lat2 = req.query["lat2"] as string
|
||||||
|
let rate = req.query["rate"] as string
|
||||||
|
if(!lon1 || !lat1 || !lon2 || !lat2){
|
||||||
|
res.status(400).send("Missing Argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!rate){
|
||||||
|
rate = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send( await callBox(lon1, lat1, lon2, lat2, rate))
|
||||||
|
}
|
||||||
|
|
16
Express/barAndCafe/src/server.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as dotenv from "dotenv";
|
||||||
|
import app from "./app";
|
||||||
|
import options from "./swaggerDef";
|
||||||
|
const swaggerJsdoc = require("swagger-jsdoc"),
|
||||||
|
swaggerUi = require("swagger-ui-express");
|
||||||
|
|
||||||
|
dotenv.config({path: '../../.env'})
|
||||||
|
|
||||||
|
const port = parseInt(process.env.BAR_PORT || '3000')
|
||||||
|
|
||||||
|
const specs = swaggerJsdoc(options)
|
||||||
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs))
|
||||||
|
|
||||||
|
app.listen(port, () =>{
|
||||||
|
console.log(`serveur running in ${port}`)
|
||||||
|
})
|
29
Express/barAndCafe/src/swaggerDef.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import * as dotenv from "dotenv";
|
||||||
|
dotenv.config({path: '../../.env'})
|
||||||
|
const port = parseInt(process.env.BAR_PORT || '3000')
|
||||||
|
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
definition: {
|
||||||
|
openapi: "3.1.0",
|
||||||
|
info: {
|
||||||
|
title: "LogRocket Express API with Swagger",
|
||||||
|
version: "0.1.0",
|
||||||
|
description:
|
||||||
|
"This is a simple CRUD API application made with Express and documented with Swagger",
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: "http://localhost:"+port.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "https://drink-tweb.cb85.fr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
apis: ['./src/*.ts','./dist/src/*.js'],
|
||||||
|
explorer: true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default options
|
31
Express/barAndCafe/test/app.test.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import request from "supertest";
|
||||||
|
import app from "../src/app";
|
||||||
|
import { Server, IncomingMessage, ServerResponse } from "http";
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config({path: '../../.env'})
|
||||||
|
const port = parseInt(process.env.BAR_PORT || '3000')
|
||||||
|
|
||||||
|
let serveur : Server<typeof IncomingMessage, typeof ServerResponse>
|
||||||
|
|
||||||
|
describe("Test the welcome path", () => {
|
||||||
|
|
||||||
|
test("It should response the 200 code for GET method", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/welcome")
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("It should response the GET method with content", done => {
|
||||||
|
const out = {hello:"world"};
|
||||||
|
request(app)
|
||||||
|
.get("/welcome")
|
||||||
|
.then(response => {
|
||||||
|
expect(response.text).toEqual(JSON.stringify(out))
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
280
Express/barAndCafe/test/openTripMaps.test.ts
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
import request from "supertest"
|
||||||
|
import app from "../src/app"
|
||||||
|
import { Server, IncomingMessage, ServerResponse } from "http"
|
||||||
|
import * as dotenv from "dotenv"
|
||||||
|
|
||||||
|
dotenv.config({path: '../../.env'})
|
||||||
|
const port = parseInt(process.env.BAR_PORT || '3000')
|
||||||
|
|
||||||
|
let serveur : Server<typeof IncomingMessage, typeof ServerResponse>
|
||||||
|
|
||||||
|
describe("Test the otm city path", () => {
|
||||||
|
|
||||||
|
test("It should response the 200 code for GET method", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/city")
|
||||||
|
.query({'name':'La roche sur yon'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the GET method with content", done => {
|
||||||
|
const out = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "562635",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-1.4344594,
|
||||||
|
46.6686478
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"xid": "N4032296324",
|
||||||
|
"name": "Le 27 point carré",
|
||||||
|
"dist": 236.40360026,
|
||||||
|
"rate": 1,
|
||||||
|
"osm": "node/4032296324",
|
||||||
|
"kinds": "foods,bars,tourist_facilities"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.get("/otm/city")
|
||||||
|
.query({'name':'La roche sur yon', "radius": 240})
|
||||||
|
.then(response => {
|
||||||
|
console.log(response.text)
|
||||||
|
expect(response.text).toEqual(JSON.stringify(out))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 400 code for GET method", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/city")
|
||||||
|
.then(response => {
|
||||||
|
console.log(response.text)
|
||||||
|
expect(response.statusCode).toBe(400)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 200 code for GET method", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/radius")
|
||||||
|
.query({'lon':'-1.4344594', 'lat' : '46.6686478', 'radius': '10'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 200 code for GET method (default radius)", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/radius")
|
||||||
|
.query({'lon':'-1.4344594', 'lat' : '46.6686478'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 400 code for GET method (missing lon and lat)", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/radius")
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(400)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("Get bar in radius from API", done => {
|
||||||
|
const out = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "562635",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-1.4344594,
|
||||||
|
46.6686478
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"xid": "N4032296324",
|
||||||
|
"name": "Le 27 point carré",
|
||||||
|
"dist": 0.17652159,
|
||||||
|
"rate": 1,
|
||||||
|
"osm": "node/4032296324",
|
||||||
|
"kinds": "foods,bars,tourist_facilities"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
request(app)
|
||||||
|
.get("/otm/radius")
|
||||||
|
.query({'lon':'-1.4344594', 'lat' : '46.6686478', 'radius': '10'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.text).toEqual(JSON.stringify(out))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 200 code for GET method", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/poidetaill")
|
||||||
|
.query({'id':'562635'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Get bar in radius from API", done => {
|
||||||
|
const out = {
|
||||||
|
"xid": "N4032296324",
|
||||||
|
"name": "Le 27 point carré",
|
||||||
|
"address": {
|
||||||
|
"road": "Rue Raymond Poincaré",
|
||||||
|
"town": "La Roche-sur-Yon",
|
||||||
|
"state": "Pays de la Loire",
|
||||||
|
"county": "La Roche-sur-Yon",
|
||||||
|
"suburb": "Zola",
|
||||||
|
"country": "France",
|
||||||
|
"postcode": "85000",
|
||||||
|
"country_code": "fr",
|
||||||
|
"house_number": "27",
|
||||||
|
"neighbourhood": "Cité des Forges"
|
||||||
|
},
|
||||||
|
"rate": "1",
|
||||||
|
"osm": "node/4032296324",
|
||||||
|
"kinds": "foods,bars,tourist_facilities",
|
||||||
|
"sources": {
|
||||||
|
"geometry": "osm",
|
||||||
|
"attributes": [
|
||||||
|
"osm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"otm": "https://opentripmap.com/en/card/N4032296324",
|
||||||
|
"point": {
|
||||||
|
"lon": -1.4344594478607178,
|
||||||
|
"lat": 46.66864776611328
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request(app)
|
||||||
|
.get("/otm/poidetaill")
|
||||||
|
.query({'id':'562635'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.text).toEqual(JSON.stringify(out))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 400 code for GET method (no id)", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/poidetaill")
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(400)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 200 code for GET method", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/box")
|
||||||
|
.query({'lon1':'-1.435199','lon2':'-1.43519', 'lat1':'46.668460', 'lat2':'46.668461'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Get bar in box from API", done => {
|
||||||
|
const out = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "562633",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-1.435197,
|
||||||
|
46.6684608
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"xid": "N4032296323",
|
||||||
|
"name": "Le Cube",
|
||||||
|
"rate": 1,
|
||||||
|
"osm": "node/4032296323",
|
||||||
|
"kinds": "foods,cafes,tourist_facilities"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
function sleep(milliseconds: number) {
|
||||||
|
const date = Date.now();
|
||||||
|
let currentDate = null;
|
||||||
|
do {
|
||||||
|
currentDate = Date.now();
|
||||||
|
} while (currentDate - date < milliseconds);
|
||||||
|
}
|
||||||
|
sleep(2000);
|
||||||
|
request(app)
|
||||||
|
.get("/otm/box")
|
||||||
|
.query({'lon1':'-1.435199','lon2':'-1.43519', 'lat1':'46.668460', 'lat2':'46.668461'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.text).toEqual(JSON.stringify(out))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Get bar in box from API lat reversed", done => {
|
||||||
|
const out = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "562633",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-1.435197,
|
||||||
|
46.6684608
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"xid": "N4032296323",
|
||||||
|
"name": "Le Cube",
|
||||||
|
"rate": 1,
|
||||||
|
"osm": "node/4032296323",
|
||||||
|
"kinds": "foods,cafes,tourist_facilities"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
request(app)
|
||||||
|
.get("/otm/box")
|
||||||
|
.query({'lon1':'-1.435199','lon2':'-1.43519', 'lat2':'46.668460', 'lat1':'46.668461'})
|
||||||
|
.then(response => {
|
||||||
|
expect(response.text).toEqual(JSON.stringify(out))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("It should response the 400 code for GET method (no coordonaite)", done => {
|
||||||
|
request(app)
|
||||||
|
.get("/otm/box")
|
||||||
|
.then(response => {
|
||||||
|
expect(response.statusCode).toBe(400)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
109
Express/barAndCafe/tsconfig.json
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
"baseUrl": "src", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
"rootDirs": ["./src"], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
18
Ratrapage_WEB.code-workspace
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"name": "Ratrapage_WEB",
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "barAndCafe",
|
||||||
|
"path": "Express/barAndCafe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "front"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
||||||
|
}
|
30
bruno/OpenData datatourisme/Google API.bru
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
meta {
|
||||||
|
name: Google API
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: https://places.googleapis.com/v1/places:searchNearby
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
X-Goog-Api-Key: {{GOOGLE_API_KEY}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"includedTypes": ["restaurant"],
|
||||||
|
"maxResultCount": 10,
|
||||||
|
"locationRestriction": {
|
||||||
|
"circle": {
|
||||||
|
"center": {
|
||||||
|
"latitude": 37.7937,
|
||||||
|
"longitude": -122.3965},
|
||||||
|
"radius": 500.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,9 @@
|
|||||||
|
vars {
|
||||||
|
TRIPMAP_URL: https://api.opentripmap.com/0.1
|
||||||
|
EXPRESS_API: http://localhost:3001
|
||||||
|
}
|
||||||
vars:secret [
|
vars:secret [
|
||||||
app_key
|
app_key,
|
||||||
|
OTM_KEY,
|
||||||
|
GOOGLE_API_KEY
|
||||||
]
|
]
|
||||||
|
8
bruno/OpenData datatourisme/environments/prod.bru
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
vars {
|
||||||
|
EXPRESS_API: https://drink-tweb.cb85.fr
|
||||||
|
PB_URL: https://pb-tweb.cb85.fr
|
||||||
|
TRIPMAP_URL: https://api.opentripmap.com/0.1/
|
||||||
|
}
|
||||||
|
vars:secret [
|
||||||
|
OTM_KEY
|
||||||
|
]
|
11
bruno/OpenData datatourisme/express Drink OTM.bru
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: express Drink OTM
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: http://localhost:3000
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
20
bruno/OpenData datatourisme/express/otm_ex_box.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_ex_box
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: http://localhost:3001/otm/box?lon1=-1.435199&lon2=-1.43519&lat1=46.668460&lat2=46.668461&apikey={{OTM_KEY}}&kinds=bars,cafes,pubs,biergartens
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
lon1: -1.435199
|
||||||
|
lon2: -1.43519
|
||||||
|
lat1: 46.668460
|
||||||
|
lat2: 46.668461
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
kinds: bars,cafes,pubs,biergartens
|
||||||
|
}
|
16
bruno/OpenData datatourisme/express/otm_ex_city.bru
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_ex_city
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{EXPRESS_API}}/otm/city?name=La roche sur yon&radius=300
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
name: La roche sur yon
|
||||||
|
radius: 300
|
||||||
|
}
|
15
bruno/OpenData datatourisme/express/otm_ex_drink_id.bru
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_ex_drink_id
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://api.opentripmap.com/0.1/en/places/xid/562635?apikey={{OTM_KEY}}
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
}
|
16
bruno/OpenData datatourisme/express/otm_ex_radius.bru
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_ex_radius
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: http://localhost:3001/otm/city?name=La roche sur yon&radius=300
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
name: La roche sur yon
|
||||||
|
radius: 300
|
||||||
|
}
|
11
bruno/OpenData datatourisme/list oaut methode.bru
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: list oaut methode
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://pb-tweb.cb85.fr/api/collections/users/auth-methods
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
18
bruno/OpenData datatourisme/oauth test.bru
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
meta {
|
||||||
|
name: oauth test
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: https://pb-tweb.cb85.fr/api/collections/users/auth-with-oauth2
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
provider: "google"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
16
bruno/OpenData datatourisme/otm/otm_city.bru
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_city
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{TRIPMAP_URL}}/en/places/geoname?name=paris&apikey={{OTM_KEY}}
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
name: paris
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
}
|
19
bruno/OpenData datatourisme/otm/otm_drink.bru
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_drink
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{TRIPMAP_URL}}/en/places/radius?radius=10&lon=-1.4344594&lat=46.6686478&apikey={{OTM_KEY}}&kinds=bars,cafes,pubs,biergartens
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
radius: 10
|
||||||
|
lon: -1.4344594
|
||||||
|
lat: 46.6686478
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
kinds: bars,cafes,pubs,biergartens
|
||||||
|
}
|
19
bruno/OpenData datatourisme/otm/otm_drink_bbox.bru
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_drink_bbox
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{TRIPMAP_URL}}/en/places/radius?radius=10&lon=-1.4344594&lat=46.6686478&apikey={{OTM_KEY}}&kinds=bars,cafes,pubs,biergartens
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
radius: 10
|
||||||
|
lon: -1.4344594
|
||||||
|
lat: 46.6686478
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
kinds: bars,cafes,pubs,biergartens
|
||||||
|
}
|
15
bruno/OpenData datatourisme/otm/otm_drink_id.bru
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_drink_id
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://api.opentripmap.com/0.1/en/places/xid/562635?apikey={{OTM_KEY}}
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
}
|
20
bruno/OpenData datatourisme/otm/otm_location.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: otm_location
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://api.opentripmap.com/0.1/en/places/bbox?lon_min=-1.435199&lon_max=-1.43519&lat_min=46.668460&lat_max=46.668461&apikey={{OTM_KEY}}&kinds=bars,cafes,pubs,biergartens
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
lon_min: -1.435199
|
||||||
|
lon_max: -1.43519
|
||||||
|
lat_min: 46.668460
|
||||||
|
lat_max: 46.668461
|
||||||
|
apikey: {{OTM_KEY}}
|
||||||
|
kinds: bars,cafes,pubs,biergartens
|
||||||
|
}
|
18
bruno/OpenData datatourisme/poketBase_api/connexion.bru
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
meta {
|
||||||
|
name: connexion
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{PB_URL}}/api/collections/users/auth-with-password
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"identity": "michel.biche@aptatio.com",
|
||||||
|
"password": "123456789"
|
||||||
|
}
|
||||||
|
}
|
27
bruno/OpenData datatourisme/poketBase_api/get all poi.bru
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: get all poi
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{PB_URL}}/api/collections/user_poi/records?filter=owner='vvy93m1hoaeshwy'&&poi_list='cabkkovmjsfoapa'
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
filter: owner
|
||||||
|
poi_list: 'cabkkovmjsfoapa'
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MTg4Mjg0NzEsImlkIjoidnZ5OTNtMWhvYWVzaHd5IiwidHlwZSI6ImF1dGhSZWNvcmQifQ.pC7u-QaZ_BYqWA5wG8wu1lRbbd4mKuKeAveWe_IBnfU
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"owner": "vvy93m1hoaeshwy",
|
||||||
|
"poi_list": "sh430u0im37cxm5"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: link user to poi
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{PB_URL}}/api/collections/user_poi/records
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MTg4Mjg0NzEsImlkIjoidnZ5OTNtMWhvYWVzaHd5IiwidHlwZSI6ImF1dGhSZWNvcmQifQ.pC7u-QaZ_BYqWA5wG8wu1lRbbd4mKuKeAveWe_IBnfU
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"owner": "vvy93m1hoaeshwy",
|
||||||
|
"poi_list": "sh430u0im37cxm5"
|
||||||
|
}
|
||||||
|
}
|
40
bruno/OpenData datatourisme/poketBase_api/send a poi.bru
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
meta {
|
||||||
|
name: send a poi
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{PB_URL}}/api/collections/POI/records
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MTg4Mjg0NzEsImlkIjoidnZ5OTNtMWhvYWVzaHd5IiwidHlwZSI6ImF1dGhSZWNvcmQifQ.pC7u-QaZ_BYqWA5wG8wu1lRbbd4mKuKeAveWe_IBnfU
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"Poi_id": "11472887",
|
||||||
|
"Poi": {
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "11472887",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-0.0626024,
|
||||||
|
51.4924088
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"xid": "W544344833",
|
||||||
|
"name": "The Blue Anchor",
|
||||||
|
"rate": 2,
|
||||||
|
"osm": "way/544344833",
|
||||||
|
"wikidata": "Q7718716",
|
||||||
|
"kinds": "pubs,foods,shops,marketplaces,tourist_facilities"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
meta {
|
||||||
|
name: send fav to astro
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: http://localhost:3000/maps/save_poi
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MTg4ODY2OTIsImlkIjoidnZ5OTNtMWhvYWVzaHd5IiwidHlwZSI6ImF1dGhSZWNvcmQifQ.R9PBGlHn6aBHt89g6G0NykMh_Vye24OpKEUYtz6R6Og
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "11472888",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-0.0626024,
|
||||||
|
51.4924088
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"xid": "W544344833",
|
||||||
|
"name": "The Blue Anchor",
|
||||||
|
"rate": 2,
|
||||||
|
"osm": "way/544344833",
|
||||||
|
"wikidata": "Q7718716",
|
||||||
|
"kinds": "pubs,foods,shops,marketplaces,tourist_facilities"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
bruno/OpenData datatourisme/test graphQL.bru
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: test graphQL
|
||||||
|
type: graphql
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: https://datatour-tweb.cb85.fr
|
||||||
|
body: graphql
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:graphql {
|
||||||
|
{
|
||||||
|
poi {
|
||||||
|
total
|
||||||
|
results {
|
||||||
|
_uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 5175e1fdf116eb7c370b629ff96aee7b308d250f
|
Subproject commit b3bc1fdc68a325905f6d466e860732466d8e750a
|
@ -1,12 +1,14 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
pocketbase:
|
pocketbase:
|
||||||
image: ghcr.io/coollabsio/pocketbase:latest
|
image: ghcr.io/coollabsio/pocketbase:latest
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_POCKETBASE_8080
|
- SERVICE_FQDN_POCKETBASE_8080
|
||||||
volumes:
|
volumes:
|
||||||
- ./.pb/pocketbase-data:/app/pb_data
|
- ./pb/pocketbase-data:/app/pb_data
|
||||||
- ./.pb/pocketbase-hooks:/app/pb_hooks
|
- ./pb/pocketbase-hooks:/app/pb_hooks
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.http.routers.poketBaseTweb.rule=Host(`${POCKET_BASE_URL}`)
|
- traefik.http.routers.poketBaseTweb.rule=Host(`${POCKET_BASE_URL}`)
|
||||||
@ -14,9 +16,12 @@ services:
|
|||||||
- traefik.http.routers.poketBaseTweb.tls.certresolver=le
|
- traefik.http.routers.poketBaseTweb.tls.certresolver=le
|
||||||
networks:
|
networks:
|
||||||
- public
|
- public
|
||||||
|
|
||||||
front:
|
front:
|
||||||
image: git.lab-ouest.org/epitech/ratrapage_t-web_front:pr-7-head
|
image: git.lab-ouest.org/epitech/ratrapage_t-web_front:pr-16-head
|
||||||
depends_on:
|
environment:
|
||||||
|
- POCKETBASE_URL=https://${POCKET_BASE_URL}
|
||||||
|
depends_on:
|
||||||
- pocketbase
|
- pocketbase
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
@ -25,10 +30,69 @@ services:
|
|||||||
- traefik.http.routers.astroTweb.tls.certresolver=le
|
- traefik.http.routers.astroTweb.tls.certresolver=le
|
||||||
networks:
|
networks:
|
||||||
- public
|
- public
|
||||||
|
|
||||||
|
back_drink:
|
||||||
|
image: git.lab-ouest.org/epitech/ratrapage_t-web_back:master
|
||||||
|
environment:
|
||||||
|
- port=${BACK_BASE_PORT}
|
||||||
|
- OPEN_TRIP_MAPS_KEY=${OPEN_TRIP_MAPS_KEY}
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.expressDrinkTweb.rule=Host(`${DRINK_URL}`)
|
||||||
|
- traefik.http.services.expressDrinkTweb.loadbalancer.server.port=${BACK_BASE_PORT}
|
||||||
|
- traefik.http.routers.expressDrinkTweb.tls.certresolver=le
|
||||||
|
networks:
|
||||||
|
- public
|
||||||
|
|
||||||
|
docs_drink:
|
||||||
|
image: git.lab-ouest.org/epitech/ratrapage_t-web_drink_jsdocs:pr-6-head
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.docsDrinkTweb.rule=Host(`${DOCS_DRINK_URL}`)
|
||||||
|
- traefik.http.services.docsDrinkTweb.loadbalancer.server.port=${DOCS_BACK_BASE_PORT}
|
||||||
|
- traefik.http.routers.docsDrinkTweb.tls.certresolver=le
|
||||||
|
networks:
|
||||||
|
- public
|
||||||
|
|
||||||
|
|
||||||
|
blazegraph:
|
||||||
|
image: conjecto/blazegraph:2.1.5
|
||||||
|
# ports:
|
||||||
|
# - 9999:9999
|
||||||
|
environment:
|
||||||
|
JAVA_OPTS: -Xms6G -Xmx6G
|
||||||
|
volumes:
|
||||||
|
- ./datatourisme/dataset:/docker-entrypoint-initdb.d
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 65536
|
||||||
|
hard: 65536
|
||||||
|
networks:
|
||||||
|
- graphQL
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
datatourisme:
|
||||||
|
image: git.lab-ouest.org/epitech/fork-open-data-tourism:master
|
||||||
|
depends_on:
|
||||||
|
- blazegraph
|
||||||
|
networks:
|
||||||
|
- graphQL
|
||||||
|
- public
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.dataTourisme.rule=Host(`${DATA_TOURISME_URL}`)
|
||||||
|
- traefik.http.services.dataTourisme.loadbalancer.server.port=${DATA_TOURISME_BASE_PORT}
|
||||||
|
- traefik.http.routers.dataTourisme.tls.certresolver=le
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
graphQL: {}
|
||||||
public:
|
public:
|
||||||
external: true
|
external: true
|
||||||
x-dockge:
|
x-dockge:
|
||||||
urls:
|
urls:
|
||||||
- https://${POCKET_BASE_URL}/
|
- https://${POCKET_BASE_URL}/
|
||||||
- https://${FRONT_URL}/
|
- https://${FRONT_URL}/
|
||||||
|
- https://${DOCS_DRINK_URL}/
|
||||||
|
- https://${DRINK_URL}/
|
||||||
|
- https://${DATA_TOURISME_URL}/
|
||||||
|
12
front/.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = false
|
8
front/.eslintignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
out
|
||||||
|
.next
|
||||||
|
|
||||||
|
next-env.d.ts
|
||||||
|
*.js
|
||||||
|
__tests__
|
324
front/.eslintrc.json
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:astro/recommended",
|
||||||
|
"plugin:@typescript-eslint/strict",
|
||||||
|
"plugin:@typescript-eslint/stylistic"
|
||||||
|
],
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.astro"
|
||||||
|
],
|
||||||
|
"parser": "astro-eslint-parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"extraFileExtensions": [
|
||||||
|
".astro"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.astro"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-extra-parens": "off",
|
||||||
|
"no-unused-expressions": "off",
|
||||||
|
"no-shadow": "off",
|
||||||
|
"quotes": "off",
|
||||||
|
"semi": "off",
|
||||||
|
"space-before-function-paren": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/prefer-for-of": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||||
|
"@typescript-eslint/space-before-function-paren": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"anonymous": "never",
|
||||||
|
"asyncArrow": "always",
|
||||||
|
"named": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/triple-slash-reference": "error",
|
||||||
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
|
||||||
|
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||||
|
"@typescript-eslint/array-type": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"default": "generic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"arrow-body-style": "error",
|
||||||
|
"arrow-parens": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/ban-types": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"types": {
|
||||||
|
"{}": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"complexity": [
|
||||||
|
"warn",
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"@typescript-eslint/consistent-type-assertions": "error",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"constructor-super": "error",
|
||||||
|
"curly": "error",
|
||||||
|
"dot-notation": "error",
|
||||||
|
"eol-last": "error",
|
||||||
|
"eqeqeq": [
|
||||||
|
"error",
|
||||||
|
"smart"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"accessibility": "explicit"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"for-direction": "error",
|
||||||
|
"getter-return": "error",
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-blacklist": [
|
||||||
|
"error",
|
||||||
|
"any",
|
||||||
|
"Number",
|
||||||
|
"number",
|
||||||
|
"String",
|
||||||
|
"string",
|
||||||
|
"Boolean",
|
||||||
|
"boolean",
|
||||||
|
"Undefined"
|
||||||
|
],
|
||||||
|
"id-length": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"exceptions": [
|
||||||
|
"_"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id-match": "error",
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
"tab",
|
||||||
|
{
|
||||||
|
"SwitchCase": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"max-classes-per-file": [
|
||||||
|
"error",
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"max-depth": [
|
||||||
|
"warn",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"max-len": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"code": 256
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-delimiter-style": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"multiline": {
|
||||||
|
"delimiter": "none",
|
||||||
|
"requireLast": true
|
||||||
|
},
|
||||||
|
"singleline": {
|
||||||
|
"delimiter": "comma",
|
||||||
|
"requireLast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
|
"new-parens": "error",
|
||||||
|
|
||||||
|
"no-async-promise-executor": "error",
|
||||||
|
"no-await-in-loop": "warn",
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-compare-neg-zero": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-constant-condition": "error",
|
||||||
|
"no-control-regex": "warn",
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-delete-var": "error",
|
||||||
|
"no-dupe-args": "error",
|
||||||
|
"no-dupe-else-if": "error",
|
||||||
|
"no-dupe-keys": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-empty": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowEmptyCatch": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-empty-character-class": "error",
|
||||||
|
"@typescript-eslint/no-empty-function": "error",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-ex-assign": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
|
||||||
|
|
||||||
|
"no-extra-boolean-cast": "error",
|
||||||
|
"no-extra-parens": "off",
|
||||||
|
"@typescript-eslint/no-extra-parens": [
|
||||||
|
"error",
|
||||||
|
"all",
|
||||||
|
{
|
||||||
|
"ignoreJSX": "all"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-extra-semi": "error",
|
||||||
|
"no-fallthrough": "off",
|
||||||
|
"no-func-assign": "error",
|
||||||
|
"no-import-assign": "error",
|
||||||
|
"no-inner-declarations": "error",
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-label-var": "error",
|
||||||
|
"no-loss-of-precision": "error",
|
||||||
|
"no-misleading-character-class": "error",
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"no-multiple-empty-lines": "error",
|
||||||
|
"@typescript-eslint/no-namespace": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-obj-calls": "error",
|
||||||
|
"no-promise-executor-return": "error",
|
||||||
|
"@typescript-eslint/no-parameter-properties": "off",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-setter-return": "error",
|
||||||
|
"@typescript-eslint/no-shadow": "error",
|
||||||
|
"no-shadow": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"builtinGlobals": false,
|
||||||
|
"hoist": "all"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-sparse-arrays": "error",
|
||||||
|
"no-template-curly-in-string": "warn",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"no-undef": "error",
|
||||||
|
"no-undef-init": "error",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-unreachable": "warn",
|
||||||
|
"no-unreachable-loop": "warn",
|
||||||
|
"no-unsafe-finally": "error",
|
||||||
|
"no-unsafe-negation": "error",
|
||||||
|
"no-unsafe-optional-chaining": "error",
|
||||||
|
"no-unused-expressions": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowTernary": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowTernary": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-unused-labels": "error",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", {
|
||||||
|
"args": "all",
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"caughtErrors": "all",
|
||||||
|
"caughtErrorsIgnorePattern": "^_",
|
||||||
|
"destructuredArrayIgnorePattern": "^_",
|
||||||
|
"varsIgnorePattern": "^_",
|
||||||
|
"ignoreRestSiblings": true
|
||||||
|
}],
|
||||||
|
"@typescript-eslint/no-non-null-assertion": ["warn"],
|
||||||
|
"no-var": "error",
|
||||||
|
"object-shorthand": [
|
||||||
|
"warn",
|
||||||
|
"methods"
|
||||||
|
],
|
||||||
|
"one-var": [
|
||||||
|
"error",
|
||||||
|
"never"
|
||||||
|
],
|
||||||
|
"prefer-const": "error",
|
||||||
|
"quote-props": [
|
||||||
|
"error",
|
||||||
|
"consistent-as-needed"
|
||||||
|
],
|
||||||
|
"quotes": "off",
|
||||||
|
"@typescript-eslint/quotes": [
|
||||||
|
"error",
|
||||||
|
"single",
|
||||||
|
{
|
||||||
|
"avoidEscape": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"radix": "error",
|
||||||
|
"require-atomic-updates": "warn",
|
||||||
|
"semi": "off",
|
||||||
|
"@typescript-eslint/semi": [
|
||||||
|
"error",
|
||||||
|
"never"
|
||||||
|
],
|
||||||
|
"space-before-function-paren": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"anonymous": "never",
|
||||||
|
"asyncArrow": "always",
|
||||||
|
"named": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spaced-comment": ["error", "always", { "block": { "exceptions": ["*"] } }],
|
||||||
|
"use-isnan": "error",
|
||||||
|
"valid-typeof": "warn"
|
||||||
|
}
|
||||||
|
}
|
10
front/.gitignore
vendored
@ -1,8 +1,6 @@
|
|||||||
# build output
|
# build output
|
||||||
dist/
|
dist/
|
||||||
|
.output/
|
||||||
# generated types
|
|
||||||
.astro/
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
@ -13,6 +11,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
# environment variables
|
# environment variables
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
@ -20,5 +19,6 @@ pnpm-debug.log*
|
|||||||
# macOS-specific files
|
# macOS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# jetbrains setting folder
|
pnpm-lock.yaml
|
||||||
.idea/
|
|
||||||
|
.astro
|
2
front/.npmrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Expose Astro dependencies for `pnpm` users
|
||||||
|
shamefully-hoist=true
|
4
front/.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
.github
|
||||||
|
.changeset
|
13
front/.prettierrc.cjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/** @type {import('prettier').Config} */
|
||||||
|
module.exports = {
|
||||||
|
printWidth: 120,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: 'es5',
|
||||||
|
useTabs: false,
|
||||||
|
|
||||||
|
plugins: [require.resolve('prettier-plugin-astro')],
|
||||||
|
|
||||||
|
overrides: [{ files: '*.astro', options: { parser: 'astro' } }],
|
||||||
|
};
|
6
front/.stackblitzrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"startCommand": "npm start",
|
||||||
|
"env": {
|
||||||
|
"ENABLE_CJS_IMPORTS": true
|
||||||
|
}
|
||||||
|
}
|
275
front/.vscode/astrowind/config-schema.json
vendored
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"site": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"site": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"trailingSlash": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"googleSiteVerificationId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "site", "base", "trailingSlash"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"default": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["default", "template"]
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"robots": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"index": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["index", "follow"]
|
||||||
|
},
|
||||||
|
"openGraph": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"site_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"type": "array",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["url", "width", "height"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["site_name", "images", "type"]
|
||||||
|
},
|
||||||
|
"twitter": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"handle": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"site": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cardType": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["handle", "site", "cardType"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title", "description", "robots", "openGraph", "twitter"]
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"language": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"textDirection": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["language", "textDirection"]
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"blog": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"postsPerPage": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"isRelatedPostsEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"relatedPostsCount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"permalink": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"robots": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"index": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["index"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["isEnabled", "permalink", "robots"]
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"pathname": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"robots": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"index": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["index"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["isEnabled", "pathname", "robots"]
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"pathname": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"robots": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"index": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["index"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["isEnabled", "pathname", "robots"]
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"isEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"pathname": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"robots": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"index": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["index"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["isEnabled", "pathname", "robots"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["isEnabled", "postsPerPage", "post", "list", "category", "tag"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["blog"]
|
||||||
|
},
|
||||||
|
"analytics": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"vendors": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"googleAnalytics": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"partytown": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["googleAnalytics"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["vendors"]
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"theme": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["theme"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["site", "metadata", "i18n", "apps", "analytics", "ui"]
|
||||||
|
}
|
8
front/.vscode/extensions.json
vendored
@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["astro-build.astro-vscode"],
|
"recommendations": [
|
||||||
|
"astro-build.astro-vscode",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"unifiedjs.vscode-mdx"
|
||||||
|
],
|
||||||
"unwantedRecommendations": []
|
"unwantedRecommendations": []
|
||||||
}
|
}
|
||||||
|
22
front/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"prettier.documentSelectors": ["**/*.astro"],
|
||||||
|
"[astro]": {
|
||||||
|
"editor.defaultFormatter": "astro-build.astro-vscode"
|
||||||
|
},
|
||||||
|
"css.customData": ["./vscode.tailwind.json"],
|
||||||
|
"eslint.validate": ["javascript", "javascriptreact", "astro", "typescript", "typescriptreact"],
|
||||||
|
"files.associations": {
|
||||||
|
"*.mdx": "markdown"
|
||||||
|
},
|
||||||
|
"editor.quickSuggestions": {
|
||||||
|
"strings": "on"
|
||||||
|
},
|
||||||
|
"tailwindCSS.includeLanguages": {
|
||||||
|
"astro": "html"
|
||||||
|
},
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"yaml.schemas": {
|
||||||
|
"./.vscode/astrowind/config-schema.json": "/src/config.yaml"
|
||||||
|
},
|
||||||
|
"eslint.experimental.useFlatConfig": true
|
||||||
|
}
|
21
front/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 onWidget
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
311
front/README.md
@ -1,54 +1,295 @@
|
|||||||
# Astro Starter Kit: Basics
|
# 🚀 AstroWind
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/onwidget/.github/main/resources/astrowind/lighthouse-score.png" align="right"
|
||||||
|
alt="AstroWind Lighthouse Score" width="100" height="358">
|
||||||
|
|
||||||
|
🌟 _Most *starred* & *forked* Astro theme in 2022 & 2023_. 🌟
|
||||||
|
|
||||||
|
**AstroWind** is a free and open-source template to make your website using **[Astro 4.0](https://astro.build/) + [Tailwind CSS](https://tailwindcss.com/)**. Ready to start a new project and designed taking into account web best practices.
|
||||||
|
|
||||||
|
- ✅ **Production-ready** scores in **PageSpeed Insights** reports.
|
||||||
|
- ✅ Integration with **Tailwind CSS** supporting **Dark mode** and **_RTL_**.
|
||||||
|
- ✅ **Fast and SEO friendly blog** with automatic **RSS feed**, **MDX** support, **Categories & Tags**, **Social Share**, ...
|
||||||
|
- ✅ **Image Optimization** (using new **Astro Assets** and **Unpic** for Universal image CDN).
|
||||||
|
- ✅ Generation of **project sitemap** based on your routes.
|
||||||
|
- ✅ **Open Graph tags** for social media sharing.
|
||||||
|
- ✅ **Analytics** built-in Google Analytics, and Splitbee integration.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/onwidget/.github/main/resources/astrowind/screenshot-astrowind-1.png" alt="AstroWind Theme Screenshot">
|
||||||
|
|
||||||
|
[](https://onwidget.com)
|
||||||
|
[](https://github.com/onwidget/astrowind/blob/main/LICENSE.md)
|
||||||
|
[](https://github.com/onwidget)
|
||||||
|
[](https://github.com/onwidget/astrowind#contributing)
|
||||||
|
[](https://snyk.io/test/github/onwidget/astrowind)
|
||||||
|
[](https://github.com/onwidget/astrowind)
|
||||||
|
[](https://github.com/onwidget/astrowind)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Table of Contents</summary>
|
||||||
|
|
||||||
|
- [Demo](#demo)
|
||||||
|
- [Upcoming: AstroWind 2.0 – We Need Your Vision!](#-upcoming-astrowind-20--we-need-your-vision)
|
||||||
|
- [Getting started](#getting-started)
|
||||||
|
- [Project structure](#project-structure)
|
||||||
|
- [Commands](#commands)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Deploy](#deploy)
|
||||||
|
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||||
|
- [Related Projects](#related-projects)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [Acknowledgements](#acknowledgements)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
📌 [https://astrowind.vercel.app/](https://astrowind.vercel.app/)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 🔔 Upcoming: AstroWind 2.0 – We Need Your Vision!
|
||||||
|
|
||||||
|
We're embarking on an exciting journey with **AstroWind 2.0**, and we want you to be a part of it! We're currently taking the first steps in developing this new version and your insights are invaluable. Join the discussion and share your feedback, ideas, and suggestions to help shape the future of **AstroWind**. Let's make **AstroWind 2.0** even better, together!
|
||||||
|
|
||||||
|
[Share Your Feedback in Our Discussion!](https://github.com/onwidget/astrowind/discussions/392)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
**AstroWind** tries to give you quick access to creating a website using [Astro 4.0](https://astro.build/) + [Tailwind CSS](https://tailwindcss.com/). It's a free theme which focuses on simplicity, good practices and high performance.
|
||||||
|
|
||||||
|
Very little vanilla javascript is used only to provide basic functionality so that each developer decides which framework (React, Vue, Svelte, Solid JS...) to use and how to approach their goals.
|
||||||
|
|
||||||
|
In this version the template supports all the options in the `output` configuration, `static`, `hybrid` and `server`, but the blog only works with `prerender = true`. We are working on the next version and aim to make it fully compatible with SSR.
|
||||||
|
|
||||||
|
### Project structure
|
||||||
|
|
||||||
|
Inside **AstroWind** template, you'll see the following folders and files:
|
||||||
|
|
||||||
```sh
|
|
||||||
npm create astro@latest -- --template basics
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
|
||||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
|
||||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
|
|
||||||
|
|
||||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 🚀 Project Structure
|
|
||||||
|
|
||||||
Inside of your Astro project, you'll see the following folders and files:
|
|
||||||
|
|
||||||
```text
|
|
||||||
/
|
/
|
||||||
├── public/
|
├── public/
|
||||||
│ └── favicon.svg
|
│ ├── _headers
|
||||||
|
│ └── robots.txt
|
||||||
├── src/
|
├── src/
|
||||||
|
│ ├── assets/
|
||||||
|
│ │ ├── favicons/
|
||||||
|
│ │ ├── images/
|
||||||
|
│ │ └── styles/
|
||||||
|
│ │ └── tailwind.css
|
||||||
│ ├── components/
|
│ ├── components/
|
||||||
│ │ └── Card.astro
|
│ │ ├── blog/
|
||||||
|
│ │ ├── common/
|
||||||
|
│ │ ├── ui/
|
||||||
|
│ │ ├── widgets/
|
||||||
|
│ │ │ ├── Header.astro
|
||||||
|
│ │ │ └── ...
|
||||||
|
│ │ ├── CustomStyles.astro
|
||||||
|
│ │ ├── Favicons.astro
|
||||||
|
│ │ └── Logo.astro
|
||||||
|
│ ├── content/
|
||||||
|
│ │ ├── post/
|
||||||
|
│ │ │ ├── post-slug-1.md
|
||||||
|
│ │ │ ├── post-slug-2.mdx
|
||||||
|
│ │ │ └── ...
|
||||||
|
│ │ └-- config.ts
|
||||||
│ ├── layouts/
|
│ ├── layouts/
|
||||||
│ │ └── Layout.astro
|
│ │ ├── Layout.astro
|
||||||
│ └── pages/
|
│ │ ├── MarkdownLayout.astro
|
||||||
│ └── index.astro
|
│ │ └── PageLayout.astro
|
||||||
└── package.json
|
│ ├── pages/
|
||||||
|
│ │ ├── [...blog]/
|
||||||
|
│ │ │ ├── [category]/
|
||||||
|
│ │ │ ├── [tag]/
|
||||||
|
│ │ │ ├── [...page].astro
|
||||||
|
│ │ │ └── index.astro
|
||||||
|
│ │ ├── index.astro
|
||||||
|
│ │ ├── 404.astro
|
||||||
|
│ │ ├-- rss.xml.ts
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── utils/
|
||||||
|
│ ├── config.yaml
|
||||||
|
│ └── navigation.js
|
||||||
|
├── package.json
|
||||||
|
├── astro.config.mjs
|
||||||
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||||
|
|
||||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||||
|
|
||||||
Any static assets, like images, can be placed in the `public/` directory.
|
Any static assets, like images, can be placed in the `public/` directory if they do not require any transformation or in the `assets/` directory if they are imported directly.
|
||||||
|
|
||||||
## 🧞 Commands
|
[](https://githubbox.com/onwidget/astrowind/tree/main) [](https://gitpod.io/?on=gitpod#https://github.com/onwidget/astrowind) [](https://stackblitz.com/github/onwidget/astrowind)
|
||||||
|
|
||||||
|
> 🧑🚀 **Seasoned astronaut?** Delete this file `README.md`. Update `src/config.yaml` and contents. Have fun!
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
| :------------------------ | :----------------------------------------------- |
|
| :-------------------- | :------------------------------------------------- |
|
||||||
| `npm install` | Installs dependencies |
|
| `npm install` | Installs dependencies |
|
||||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
| `npm run build` | Build your production site to `./dist/` |
|
||||||
| `npm run preview` | Preview your build locally, before deploying |
|
| `npm run preview` | Preview your build locally, before deploying |
|
||||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
| `npm run format` | Format codes with Prettier |
|
||||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
| `npm run lint:eslint` | Run Eslint |
|
||||||
|
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||||
|
|
||||||
## 👀 Want to learn more?
|
<br>
|
||||||
|
|
||||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
### Configuration
|
||||||
|
|
||||||
|
Basic configuration file: `./src/config.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
site:
|
||||||
|
name: 'Example'
|
||||||
|
site: 'https://example.com'
|
||||||
|
base: '/' # Change this if you need to deploy to Github Pages, for example
|
||||||
|
trailingSlash: false # Generate permalinks with or without "/" at the end
|
||||||
|
|
||||||
|
googleSiteVerificationId: false # Or some value,
|
||||||
|
|
||||||
|
# Default SEO metadata
|
||||||
|
metadata:
|
||||||
|
title:
|
||||||
|
default: 'Example'
|
||||||
|
template: '%s — Example'
|
||||||
|
description: 'This is the default meta description of Example website'
|
||||||
|
robots:
|
||||||
|
index: true
|
||||||
|
follow: true
|
||||||
|
openGraph:
|
||||||
|
site_name: 'Example'
|
||||||
|
images:
|
||||||
|
- url: '~/assets/images/default.png'
|
||||||
|
width: 1200
|
||||||
|
height: 628
|
||||||
|
type: website
|
||||||
|
twitter:
|
||||||
|
handle: '@twitter_user'
|
||||||
|
site: '@twitter_user'
|
||||||
|
cardType: summary_large_image
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
language: en
|
||||||
|
textDirection: ltr
|
||||||
|
|
||||||
|
apps:
|
||||||
|
blog:
|
||||||
|
isEnabled: true # If the blog will be enabled
|
||||||
|
postsPerPage: 6 # Number of posts per page
|
||||||
|
|
||||||
|
post:
|
||||||
|
isEnabled: true
|
||||||
|
permalink: '/blog/%slug%' # Variables: %slug%, %year%, %month%, %day%, %hour%, %minute%, %second%, %category%
|
||||||
|
robots:
|
||||||
|
index: true
|
||||||
|
|
||||||
|
list:
|
||||||
|
isEnabled: true
|
||||||
|
pathname: 'blog' # Blog main path, you can change this to "articles" (/articles)
|
||||||
|
robots:
|
||||||
|
index: true
|
||||||
|
|
||||||
|
category:
|
||||||
|
isEnabled: true
|
||||||
|
pathname: 'category' # Category main path /category/some-category, you can change this to "group" (/group/some-category)
|
||||||
|
robots:
|
||||||
|
index: true
|
||||||
|
|
||||||
|
tag:
|
||||||
|
isEnabled: true
|
||||||
|
pathname: 'tag' # Tag main path /tag/some-tag, you can change this to "topics" (/topics/some-category)
|
||||||
|
robots:
|
||||||
|
index: false
|
||||||
|
|
||||||
|
isRelatedPostsEnabled: true # If a widget with related posts is to be displayed below each post
|
||||||
|
relatedPostsCount: 4 # Number of related posts to display
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
vendors:
|
||||||
|
googleAnalytics:
|
||||||
|
id: null # or "G-XXXXXXXXXX"
|
||||||
|
|
||||||
|
ui:
|
||||||
|
theme: 'system' # Values: "system" | "light" | "dark" | "light:only" | "dark:only"
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
#### Customize Design
|
||||||
|
|
||||||
|
To customize Font families, Colors or more Elements refer to the following files:
|
||||||
|
|
||||||
|
- `src/components/CustomStyles.astro`
|
||||||
|
- `src/assets/styles/tailwind.css`
|
||||||
|
|
||||||
|
### Deploy
|
||||||
|
|
||||||
|
#### Deploy to production (manual)
|
||||||
|
|
||||||
|
You can create an optimized production build with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, your website is ready to be deployed. All generated files are located at
|
||||||
|
`dist` folder, which you can deploy the folder to any hosting service you
|
||||||
|
prefer.
|
||||||
|
|
||||||
|
#### Deploy to Netlify
|
||||||
|
|
||||||
|
Clone this repository on own GitHub account and deploy to Netlify:
|
||||||
|
|
||||||
|
[](https://app.netlify.com/start/deploy?repository=https://github.com/onwidget/astrowind)
|
||||||
|
|
||||||
|
#### Deploy to Vercel
|
||||||
|
|
||||||
|
Clone this repository on own GitHub account and deploy to Vercel:
|
||||||
|
|
||||||
|
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fonwidget%2Fastrowind)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
- Why?
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Related projects
|
||||||
|
|
||||||
|
- [TailNext](https://tailnext.vercel.app/) - Free template using Next.js 14 and Tailwind CSS with the new App Router.
|
||||||
|
- [Qwind](https://qwind.pages.dev/) - Free template to make your website using Qwik + Tailwind CSS.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you have any idea, suggestions or find any bugs, feel free to open a discussion, an issue or create a pull request.
|
||||||
|
That would be very useful for all of us and we would be happy to listen and take action.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
Initially created by [onWidget](https://onwidget.com) and maintained by a community of [contributors](https://github.com/onwidget/astrowind/graphs/contributors).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
**AstroWind** is licensed under the MIT license — see the [LICENSE](./LICENSE.md) file for details.
|
||||||
|
@ -1,30 +1,98 @@
|
|||||||
import { defineConfig } from 'astro/config';
|
import path from 'path';
|
||||||
import node from '@astrojs/node';
|
import { fileURLToPath } from 'url';
|
||||||
import tailwind from "@astrojs/tailwind";
|
import node from '@astrojs/node'
|
||||||
|
import tailwind from '@astrojs/tailwind'
|
||||||
|
import { defineConfig, squooshImageService } from 'astro/config';
|
||||||
|
|
||||||
import react from "@astrojs/react";
|
import sitemap from '@astrojs/sitemap';
|
||||||
|
import tailwind from '@astrojs/tailwind';
|
||||||
|
import mdx from '@astrojs/mdx';
|
||||||
|
import partytown from '@astrojs/partytown';
|
||||||
|
import icon from 'astro-icon';
|
||||||
|
import compress from '@playform/compress';
|
||||||
|
|
||||||
|
import astrowind from './vendor/integration';
|
||||||
|
|
||||||
|
import {
|
||||||
|
readingTimeRemarkPlugin,
|
||||||
|
responsiveTablesRehypePlugin,
|
||||||
|
lazyImagesRehypePlugin,
|
||||||
|
} from './src/utils/frontmatter.mjs';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
const hasExternalScripts = false;
|
||||||
|
const whenExternalScripts = (items = []) =>
|
||||||
|
hasExternalScripts ? (Array.isArray(items) ? items.map((item) => item()) : [items()]) : [];
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// integrations: [tailwind(), test, routing(), version(), buildInfos()],
|
|
||||||
compressHTML: true,
|
|
||||||
build: {
|
|
||||||
assets: 'assets',
|
|
||||||
inlineStylesheets: 'auto'
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
host: true,
|
|
||||||
port: 3000
|
|
||||||
},
|
|
||||||
trailingSlash: 'never',
|
|
||||||
output: 'server',
|
output: 'server',
|
||||||
adapter: node({
|
compressHTML: true,
|
||||||
mode: 'standalone'
|
build: {
|
||||||
}),
|
assets: 'assets',
|
||||||
integrations: [tailwind(), react()],
|
inlineStylesheets: 'auto'
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 3000
|
||||||
|
},
|
||||||
|
trailingSlash: 'never',
|
||||||
|
adapter: node({
|
||||||
|
mode: 'standalone'
|
||||||
|
}),
|
||||||
|
integrations: [
|
||||||
|
tailwind({
|
||||||
|
applyBaseStyles: false,
|
||||||
|
}),
|
||||||
|
sitemap(),
|
||||||
|
mdx(),
|
||||||
|
icon({
|
||||||
|
include: {
|
||||||
|
tabler: ['*'],
|
||||||
|
'flat-color-icons': [
|
||||||
|
'template',
|
||||||
|
'gallery',
|
||||||
|
'approval',
|
||||||
|
'document',
|
||||||
|
'advertising',
|
||||||
|
'currency-exchange',
|
||||||
|
'voice-presentation',
|
||||||
|
'business-contact',
|
||||||
|
'database',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
...whenExternalScripts(() =>
|
||||||
|
partytown({
|
||||||
|
config: { forward: ['dataLayer.push'] },
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
astrowind({
|
||||||
|
config: './src/config.yaml',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
image: {
|
||||||
|
service: squooshImageService(),
|
||||||
|
domains: ['cdn.pixabay.com'],
|
||||||
|
},
|
||||||
|
|
||||||
|
markdown: {
|
||||||
|
remarkPlugins: [readingTimeRemarkPlugin],
|
||||||
|
rehypePlugins: [responsiveTablesRehypePlugin, lazyImagesRehypePlugin],
|
||||||
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['leaflet']
|
include: ['leaflet']
|
||||||
}
|
},
|
||||||
}
|
resolve: {
|
||||||
});
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
59
front/eslint.config.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import astroEslintParser from 'astro-eslint-parser';
|
||||||
|
import eslintPluginAstro from 'eslint-plugin-astro';
|
||||||
|
import globals from 'globals';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import typescriptParser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
...eslintPluginAstro.configs['flat/recommended'],
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.astro'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: astroEslintParser,
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extraFileExtensions: ['.astro'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx,astro}'],
|
||||||
|
rules: {
|
||||||
|
'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Define the configuration for `<script>` tag.
|
||||||
|
// Script in `<script>` is assigned a virtual file name with the `.js` extension.
|
||||||
|
files: ['**/*.{ts,tsx}', '**/*.astro/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: typescriptParser,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Note: you must disable the base rule as it can report incorrect errors
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
destructuredArrayIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ['dist', 'node_modules', '.github', 'types.generated.d.ts', '.astro'],
|
||||||
|
},
|
||||||
|
];
|
9
front/netlify.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[build]
|
||||||
|
publish = "dist"
|
||||||
|
command = "npm run build"
|
||||||
|
[build.processing.html]
|
||||||
|
pretty_urls = false
|
||||||
|
[[headers]]
|
||||||
|
for = "/_astro/*"
|
||||||
|
[headers.values]
|
||||||
|
Cache-Control = "public, max-age=31536000, immutable"
|
8391
front/package-lock.json
generated
@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "front",
|
"name": "front",
|
||||||
|
"version": "1.0.0-beta.34",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"private": true,
|
||||||
"private": "true",
|
"engines": {
|
||||||
|
"node": "^18.17.1 || ^20.3.0 || >= 21.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "node ./dist/server/entry.mjs",
|
"start": "node ./dist/server/entry.mjs",
|
||||||
@ -13,32 +16,64 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^8.2.5",
|
"@astrojs/node": "^8.2.5",
|
||||||
"@astrojs/react": "^3.3.1",
|
"@astrojs/rss": "^4.0.5",
|
||||||
|
"@astrojs/sitemap": "^3.1.4",
|
||||||
"@astrojs/tailwind": "^5.1.0",
|
"@astrojs/tailwind": "^5.1.0",
|
||||||
|
"@astrolib/analytics": "^0.5.0",
|
||||||
|
"@astrolib/seo": "^1.0.0-beta.5",
|
||||||
|
"@fontsource-variable/inter": "^5.0.18",
|
||||||
"@tailwindcss/typography": "^0.5.12",
|
"@tailwindcss/typography": "^0.5.12",
|
||||||
"@types/react": "^18.2.79",
|
"astro": "^4.8.3",
|
||||||
"@types/react-dom": "^18.2.25",
|
"astro-embed": "^0.7.2",
|
||||||
"astro": "4.5.12",
|
"astro-icon": "^1.1.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet-control-geocoder": "^2.4.0",
|
||||||
|
"leaflet-geosearch": "^4.0.0",
|
||||||
|
"leaflet-routing-machine": "^3.2.12",
|
||||||
|
"limax": "4.1.0",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
"lucide-astro": "^0.372.0",
|
"lucide-astro": "^0.372.0",
|
||||||
"pocketbase": "^0.21.1",
|
"pocketbase": "^0.21.1",
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-leaflet": "^4.2.1",
|
"react-leaflet": "^4.2.1",
|
||||||
"simple-icons-astro": "^11.12.0",
|
"simple-icons-astro": "^11.12.0",
|
||||||
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5.2.2"
|
"unpic": "^3.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/check": "^0",
|
"@astrojs/check": "^0",
|
||||||
|
"@astrojs/mdx": "^3.0.0",
|
||||||
|
"@astrojs/partytown": "^2.1.0",
|
||||||
|
"@astrojs/tailwind": "5.1.0",
|
||||||
|
"@eslint/js": "^9.2.0",
|
||||||
|
"@iconify-json/flat-color-icons": "^1.1.10",
|
||||||
|
"@iconify-json/tabler": "^1.1.111",
|
||||||
|
"@playform/compress": "0.0.4",
|
||||||
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
|
"@types/eslint__js": "^8.42.3",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/leaflet": "^1.9.12",
|
"@types/leaflet": "^1.9.12",
|
||||||
|
"@types/leaflet-routing-machine": "^3.2.8",
|
||||||
|
"@types/lodash.merge": "^4.6.9",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@vitest/coverage-v8": "^1",
|
"@vitest/coverage-v8": "^1",
|
||||||
|
"astro-eslint-parser": "^1.0.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-astro": "^0.31.4",
|
"eslint-plugin-astro": "^0.31.4",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
|
"globals": "^15.2.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"mdast-util-to-string": "^4.0.0",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"prettier-plugin-astro": "^0.13.0",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
|
"rehype-plugin-image-native-lazy-loading": "^1.2.0",
|
||||||
|
"sharp": "0.33.3",
|
||||||
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
"typescript-eslint": "^7.9.0",
|
||||||
"vitest": "^1"
|
"vitest": "^1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
front/public/_headers
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/_astro/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
29
front/public/decapcms/config.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
backend:
|
||||||
|
name: git-gateway
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
media_folder: 'src/assets/images'
|
||||||
|
public_folder: '/_astro'
|
||||||
|
|
||||||
|
collections:
|
||||||
|
- name: 'post'
|
||||||
|
label: 'Post'
|
||||||
|
folder: 'src/content/post'
|
||||||
|
create: true
|
||||||
|
fields:
|
||||||
|
- { label: 'Title', name: 'title', widget: 'string' }
|
||||||
|
- { label: 'Excerpt', name: 'excerpt', widget: 'string' }
|
||||||
|
- { label: 'Category', name: 'category', widget: 'string' }
|
||||||
|
- {
|
||||||
|
label: 'Tags',
|
||||||
|
name: 'tags',
|
||||||
|
widget: 'list',
|
||||||
|
allow_add: true,
|
||||||
|
allow_delete: true,
|
||||||
|
collapsed: false,
|
||||||
|
field: { label: 'Tag', name: 'tag', widget: 'string' },
|
||||||
|
}
|
||||||
|
- { label: 'Image', name: 'image', widget: 'string' }
|
||||||
|
- { label: 'Publish Date', name: 'publishDate', widget: 'datetime', required: false }
|
||||||
|
- { label: 'Author', name: 'author', widget: 'string' }
|
||||||
|
- { label: 'Content', name: 'body', widget: 'markdown' }
|
14
front/public/decapcms/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<title>Content Manager</title>
|
||||||
|
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Include the script that builds the page and powers Decap CMS -->
|
||||||
|
<script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
front/public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
11
front/sandbox.config.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"infiniteLoopProtection": true,
|
||||||
|
"hardReloadOnChange": false,
|
||||||
|
"view": "browser",
|
||||||
|
"template": "node",
|
||||||
|
"container": {
|
||||||
|
"port": 3000,
|
||||||
|
"startScript": "start",
|
||||||
|
"node": "18"
|
||||||
|
}
|
||||||
|
}
|
BIN
front/src/assets/favicons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
front/src/assets/favicons/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
9
front/src/assets/favicons/favicon.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||||
|
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||||
|
<style>
|
||||||
|
path { fill: #000; }
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path { fill: #FFF; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 749 B |
BIN
front/src/assets/images/app-store.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
front/src/assets/images/default.png
Normal file
After Width: | Height: | Size: 563 KiB |
BIN
front/src/assets/images/google-play.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
front/src/assets/images/hero-image.png
Normal file
After Width: | Height: | Size: 539 KiB |
92
front/src/assets/styles/tailwind.css
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.bg-page {
|
||||||
|
background-color: var(--aw-color-bg-page);
|
||||||
|
}
|
||||||
|
.bg-dark {
|
||||||
|
background-color: var(--aw-color-bg-page-dark);
|
||||||
|
}
|
||||||
|
.bg-light {
|
||||||
|
background-color: var(--aw-color-bg-page);
|
||||||
|
}
|
||||||
|
.text-page {
|
||||||
|
color: var(--aw-color-text-page);
|
||||||
|
}
|
||||||
|
.text-muted {
|
||||||
|
color: var(--aw-color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.btn {
|
||||||
|
@apply inline-flex items-center justify-center rounded-full border-gray-400 border bg-transparent font-medium text-center text-base text-page leading-snug transition py-3.5 px-6 md:px-8 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
@apply btn font-semibold bg-primary text-white border-primary hover:bg-secondary hover:border-secondary hover:text-white dark:text-white dark:bg-primary dark:border-primary dark:hover:border-secondary dark:hover:bg-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
@apply btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-tertiary {
|
||||||
|
@apply btn border-none shadow-none text-muted hover:text-gray-900 dark:text-gray-400 dark:hover:text-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#header.scroll > div:first-child {
|
||||||
|
@apply bg-page md:bg-white/90 md:backdrop-blur-md;
|
||||||
|
box-shadow: 0 0.375rem 1.5rem 0 rgb(140 152 164 / 13%);
|
||||||
|
}
|
||||||
|
.dark #header.scroll > div:first-child,
|
||||||
|
#header.scroll.dark > div:first-child {
|
||||||
|
@apply bg-page md:bg-[#030621e6] border-b border-gray-500/20;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
/* #header.scroll > div:last-child {
|
||||||
|
@apply py-3;
|
||||||
|
} */
|
||||||
|
|
||||||
|
#header.expanded nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 70px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 70px !important;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:focus .dropdown-menu,
|
||||||
|
.dropdown:focus-within .dropdown-menu,
|
||||||
|
.dropdown:hover .dropdown-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[astro-icon].icon-light > * {
|
||||||
|
stroke-width: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[astro-icon].icon-bold > * {
|
||||||
|
stroke-width: 2.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-aw-toggle-menu] path {
|
||||||
|
@apply transition;
|
||||||
|
}
|
||||||
|
[data-aw-toggle-menu].expanded g > path:first-child {
|
||||||
|
@apply -rotate-45 translate-y-[15px] translate-x-[-3px];
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-aw-toggle-menu].expanded g > path:last-child {
|
||||||
|
@apply rotate-45 translate-y-[-8px] translate-x-[14px];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To deprecated */
|
||||||
|
|
||||||
|
.dd *:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
30
front/src/components/CheckBox.astro
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
import type { Input as Props } from 'types';
|
||||||
|
|
||||||
|
const {value, checked ,name, label, autocomplete, placeholder, divClass, inputClass} = Astro.props;
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
name && (
|
||||||
|
<div class={"flex flex-row items-center " + divClass}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name={name}
|
||||||
|
id={name}
|
||||||
|
checked={checked}
|
||||||
|
value={value}
|
||||||
|
autocomplete={autocomplete}
|
||||||
|
placeholder={placeholder}
|
||||||
|
class={"mr-2 size-5 " + inputClass}
|
||||||
|
/>
|
||||||
|
{label && (
|
||||||
|
<label for={name} class="block text-lg font-medium">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
63
front/src/components/CustomStyles.astro
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
import '@fontsource-variable/inter';
|
||||||
|
|
||||||
|
// 'DM Sans'
|
||||||
|
// Nunito
|
||||||
|
// Dosis
|
||||||
|
// Outfit
|
||||||
|
// Roboto
|
||||||
|
// Literata
|
||||||
|
// 'IBM Plex Sans'
|
||||||
|
// Karla
|
||||||
|
// Poppins
|
||||||
|
// 'Fira Sans'
|
||||||
|
// 'Libre Franklin'
|
||||||
|
// Inconsolata
|
||||||
|
// Raleway
|
||||||
|
// Oswald
|
||||||
|
// 'Space Grotesk'
|
||||||
|
// Urbanist
|
||||||
|
---
|
||||||
|
|
||||||
|
<style is:inline>
|
||||||
|
:root {
|
||||||
|
--aw-font-sans: 'InterVariable';
|
||||||
|
--aw-font-serif: 'InterVariable';
|
||||||
|
--aw-font-heading: 'InterVariable';
|
||||||
|
|
||||||
|
--aw-color-primary: rgb(1 97 239);
|
||||||
|
--aw-color-secondary: rgb(1 84 207);
|
||||||
|
--aw-color-accent: rgb(109 40 217);
|
||||||
|
|
||||||
|
--aw-color-text-heading: rgb(0 0 0);
|
||||||
|
--aw-color-text-default: rgb(16 16 16);
|
||||||
|
--aw-color-text-muted: rgb(16 16 16 / 66%);
|
||||||
|
--aw-color-bg-page: rgb(255 255 255);
|
||||||
|
|
||||||
|
--aw-color-bg-page-dark: rgb(3 6 32);
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: lavender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--aw-font-sans: 'InterVariable';
|
||||||
|
--aw-font-serif: 'InterVariable';
|
||||||
|
--aw-font-heading: 'InterVariable';
|
||||||
|
|
||||||
|
--aw-color-primary: rgb(1 97 239);
|
||||||
|
--aw-color-secondary: rgb(1 84 207);
|
||||||
|
--aw-color-accent: rgb(109 40 217);
|
||||||
|
|
||||||
|
--aw-color-text-heading: rgb(247, 248, 248);
|
||||||
|
--aw-color-text-default: rgb(229 236 246);
|
||||||
|
--aw-color-text-muted: rgb(229 236 246 / 66%);
|
||||||
|
--aw-color-bg-page: rgb(3 6 32);
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: black;
|
||||||
|
color: snow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
10
front/src/components/Favicons.astro
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
import favIcon from 'assets/favicons/favicon.ico';
|
||||||
|
import favIconSvg from 'assets/favicons/favicon.svg';
|
||||||
|
import appleTouchIcon from 'assets/favicons/apple-touch-icon.png';
|
||||||
|
---
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href={favIcon} />
|
||||||
|
<link rel="icon" type="image/svg+xml" href={favIconSvg.src} />
|
||||||
|
<link rel="mask-icon" href={favIconSvg.src} color="#8D46E7" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href={appleTouchIcon.src} />
|
28
front/src/components/Input.astro
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
import type { Input as Props } from 'types';
|
||||||
|
|
||||||
|
const { type, name, label, autocomplete, placeholder, divClass, inputClass} = Astro.props;
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
name && (
|
||||||
|
<div class={divClass}>
|
||||||
|
{label && (
|
||||||
|
<label for={name} class="block text-sm font-medium">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
name={name}
|
||||||
|
id={name}
|
||||||
|
autocomplete={autocomplete}
|
||||||
|
placeholder={placeholder}
|
||||||
|
class={"py-3 px-4 block w-full text-md rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-900" + inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
9
front/src/components/Logo.astro
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
import { SITE } from 'astrowind:config';
|
||||||
|
---
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="self-center ml-2 rtl:ml-0 rtl:mr-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white"
|
||||||
|
>
|
||||||
|
🚀 {SITE?.name}
|
||||||
|
</span>
|
53
front/src/components/Oauth.astro
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
import ContactUs from 'components/widgets/Contact.astro';
|
||||||
|
import CallToAction from 'components/widgets/CallToAction.astro';
|
||||||
|
|
||||||
|
const pb = Astro.locals.pb
|
||||||
|
const oauths = (await pb.collection('users').listAuthMethods()).authProviders;
|
||||||
|
const discordProvider = oauths.find(item => item.name === 'discord');
|
||||||
|
const googleProvider = oauths.find(item => item.name === 'google');
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<>
|
||||||
|
<CallToAction
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
variant: 'primary',
|
||||||
|
text: 'Discord',
|
||||||
|
href: discordProvider!.authUrl + Astro.url.protocol + "//" + Astro.url.host + '/account/oauth',
|
||||||
|
icon: 'tabler:brand-discord',
|
||||||
|
class: "oauth-btn",
|
||||||
|
"data-cookie": encodeURIComponent(JSON.stringify(discordProvider))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'primary',
|
||||||
|
text: 'Google',
|
||||||
|
href: googleProvider!.authUrl + Astro.url.protocol + "//" + Astro.url.host + '/account/oauth',
|
||||||
|
icon: 'tabler:brand-google',
|
||||||
|
class: "oauth-btn",
|
||||||
|
"data-cookie": encodeURIComponent(JSON.stringify(googleProvider))
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Fragment slot="title">
|
||||||
|
Oauth
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<Fragment slot="subtitle">
|
||||||
|
Connecter Vous aussi avec
|
||||||
|
</Fragment>
|
||||||
|
</CallToAction>
|
||||||
|
</>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import { date } from "astro/zod";
|
||||||
|
|
||||||
|
document.cookie = "provider" + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||||
|
// console.log(date.toString)
|
||||||
|
|
||||||
|
const btn = document.querySelectorAll('.oauth-btn')
|
||||||
|
btn.forEach((item: Element) =>(item.addEventListener('click', (ev) =>{
|
||||||
|
document.cookie = "provider" + "=" + item.getAttribute('data-cookie') + "; path=/;"// expires=" + ;
|
||||||
|
})))
|
||||||
|
</script>
|
19
front/src/components/Radios.astro
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
values: Array<{label:String, name:string, checked?:boolean | undefined}>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fields = Astro.props.values
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<>
|
||||||
|
<div class="flex, flex-col">
|
||||||
|
{fields.map(value => (
|
||||||
|
<input type="radio" id={value.name} name="note-min" value={value.name} checked={value.checked}>
|
||||||
|
<label for={value.name} class="ml-1">{value.label}</label><br>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
14
front/src/components/blog/Grid.astro
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
import Item from 'components/blog/GridItem.astro';
|
||||||
|
import type { Post } from 'types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
posts: Array<Post>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { posts } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="grid gap-6 row-gap-5 md:grid-cols-2 lg:grid-cols-4 -mb-6">
|
||||||
|
{posts.map((post) => <Item post={post} />)}
|
||||||
|
</div>
|
69
front/src/components/blog/GridItem.astro
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
import { APP_BLOG } from 'astrowind:config';
|
||||||
|
import type { Post } from 'types';
|
||||||
|
|
||||||
|
import Image from 'components/common/Image.astro';
|
||||||
|
|
||||||
|
import { findImage } from 'utils/images';
|
||||||
|
import { getPermalink } from 'utils/permalinks';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
post: Post;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
const image = await findImage(post.image);
|
||||||
|
|
||||||
|
const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') : '';
|
||||||
|
---
|
||||||
|
|
||||||
|
<article class="mb-6 transition">
|
||||||
|
<div class="relative md:h-64 bg-gray-400 dark:bg-slate-700 rounded shadow-lg mb-6">
|
||||||
|
{
|
||||||
|
image &&
|
||||||
|
(link ? (
|
||||||
|
<a href={link}>
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
class="w-full md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
||||||
|
widths={[400, 900]}
|
||||||
|
width={400}
|
||||||
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
|
alt={post.title}
|
||||||
|
aspectRatio="16:9"
|
||||||
|
layout="cover"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
class="w-full md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
||||||
|
widths={[400, 900]}
|
||||||
|
width={400}
|
||||||
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
|
alt={post.title}
|
||||||
|
aspectRatio="16:9"
|
||||||
|
layout="cover"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-xl sm:text-2xl font-bold leading-tight mb-2 font-heading dark:text-slate-300">
|
||||||
|
{
|
||||||
|
link ? (
|
||||||
|
<a class="inline-block hover:text-primary dark:hover:text-blue-700 transition ease-in duration-200" href={link}>
|
||||||
|
{post.title}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
post.title
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p class="text-muted dark:text-slate-400 text-lg">{post.excerpt}</p>
|
||||||
|
</article>
|
12
front/src/components/blog/Headline.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
const { title = await Astro.slots.render('default'), subtitle = await Astro.slots.render('subtitle') } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<header class="mb-8 md:mb-16 text-center max-w-3xl mx-auto">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter font-heading" set:html={title} />
|
||||||
|
{
|
||||||
|
subtitle && (
|
||||||
|
<div class="mt-2 md:mt-3 mx-auto text-xl text-gray-500 dark:text-slate-400 font-medium" set:html={subtitle} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</header>
|
20
front/src/components/blog/List.astro
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
import Item from 'components/blog/ListItem.astro';
|
||||||
|
import type { Post } from 'types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
posts: Array<Post>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { posts } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
posts.map((post) => (
|
||||||
|
<li class="mb-12 md:mb-20">
|
||||||
|
<Item post={post} />
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
118
front/src/components/blog/ListItem.astro
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import Image from 'components/common/Image.astro';
|
||||||
|
import PostTags from 'components/blog/Tags.astro';
|
||||||
|
|
||||||
|
import { APP_BLOG } from 'astrowind:config';
|
||||||
|
import type { Post } from 'types';
|
||||||
|
|
||||||
|
import { getPermalink } from 'utils/permalinks';
|
||||||
|
import { findImage } from 'utils/images';
|
||||||
|
import { getFormattedDate } from 'utils/utils';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
post: Post;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
const image = (await findImage(post.image)) as ImageMetadata | undefined;
|
||||||
|
|
||||||
|
const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') : '';
|
||||||
|
---
|
||||||
|
|
||||||
|
<article class={`max-w-md mx-auto md:max-w-none grid gap-6 md:gap-8 ${image ? 'md:grid-cols-2' : ''}`}>
|
||||||
|
{
|
||||||
|
image &&
|
||||||
|
(link ? (
|
||||||
|
<a class="relative block group" href={link ?? 'javascript:void(0)'}>
|
||||||
|
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
|
||||||
|
{image && (
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
||||||
|
widths={[400, 900]}
|
||||||
|
width={900}
|
||||||
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
|
alt={post.title}
|
||||||
|
aspectRatio="16:9"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
|
||||||
|
{image && (
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
|
||||||
|
widths={[400, 900]}
|
||||||
|
width={900}
|
||||||
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
|
alt={post.title}
|
||||||
|
aspectRatio="16:9"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<div class="mt-2">
|
||||||
|
<header>
|
||||||
|
<div class="mb-1">
|
||||||
|
<span class="text-sm">
|
||||||
|
<Icon name="tabler:clock" class="w-3.5 h-3.5 inline-block -mt-0.5 dark:text-gray-400" />
|
||||||
|
<time datetime={String(post.publishDate)} class="inline-block">{getFormattedDate(post.publishDate)}</time>
|
||||||
|
{
|
||||||
|
post.author && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
· <Icon name="tabler:user" class="w-3.5 h-3.5 inline-block -mt-0.5 dark:text-gray-400" />
|
||||||
|
<span>{post.author.replaceAll('-', ' ')}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
post.category && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
·{' '}
|
||||||
|
<a class="hover:underline" href={getPermalink(post.category.slug, 'category')}>
|
||||||
|
{post.category.title}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl sm:text-2xl font-bold leading-tight mb-2 font-heading dark:text-slate-300">
|
||||||
|
{
|
||||||
|
link ? (
|
||||||
|
<a
|
||||||
|
class="inline-block hover:text-primary dark:hover:text-blue-700 transition ease-in duration-200"
|
||||||
|
href={link}
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
post.title
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{post.excerpt && <p class="flex-grow text-muted dark:text-slate-400 text-lg">{post.excerpt}</p>}
|
||||||
|
{
|
||||||
|
post.tags && Array.isArray(post.tags) ? (
|
||||||
|
<footer class="mt-5">
|
||||||
|
<PostTags tags={post.tags} />
|
||||||
|
</footer>
|
||||||
|
) : (
|
||||||
|
<Fragment />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</article>
|
36
front/src/components/blog/Pagination.astro
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import { getPermalink } from 'utils/permalinks';
|
||||||
|
import Button from 'components/ui/Button.astro';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
prevUrl?: string;
|
||||||
|
nextUrl?: string;
|
||||||
|
prevText?: string;
|
||||||
|
nextText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { prevUrl, nextUrl, prevText = 'Newer posts', nextText = 'Older posts' } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
(prevUrl || nextUrl) && (
|
||||||
|
<div class="container flex">
|
||||||
|
<div class="flex flex-row mx-auto container justify-between">
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
class={`md:px-3 px-3 mr-2 ${!prevUrl ? 'invisible' : ''}`}
|
||||||
|
href={getPermalink(prevUrl)}
|
||||||
|
>
|
||||||
|
<Icon name="tabler:chevron-left" class="w-6 h-6" />
|
||||||
|
<p class="ml-2">{prevText}</p>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button variant="tertiary" class={`md:px-3 px-3 ${!nextUrl ? 'invisible' : ''}`} href={getPermalink(nextUrl)}>
|
||||||
|
<span class="mr-2">{nextText}</span>
|
||||||
|
<Icon name="tabler:chevron-right" class="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
28
front/src/components/blog/RelatedPosts.astro
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
import { APP_BLOG } from 'astrowind:config';
|
||||||
|
|
||||||
|
import { getRelatedPosts } from 'utils/blog';
|
||||||
|
import BlogHighlightedPosts from '../widgets/BlogHighlightedPosts.astro';
|
||||||
|
import type { Post } from 'types';
|
||||||
|
import { getBlogPermalink } from 'utils/permalinks';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
post: Post;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
|
||||||
|
const relatedPosts = post.tags ? await getRelatedPosts(post, 4) : [];
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
APP_BLOG.isRelatedPostsEnabled ? (
|
||||||
|
<BlogHighlightedPosts
|
||||||
|
classes={{ container: 'pt-0 lg:pt-0 md:pt-0' }}
|
||||||
|
title="Related Posts"
|
||||||
|
linkText="View All Posts"
|
||||||
|
linkUrl={getBlogPermalink()}
|
||||||
|
postIds={relatedPosts.map((post) => post.id)}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
99
front/src/components/blog/SinglePost.astro
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
|
import Image from 'components/common/Image.astro';
|
||||||
|
import PostTags from 'components/blog/Tags.astro';
|
||||||
|
import SocialShare from 'components/common/SocialShare.astro';
|
||||||
|
|
||||||
|
import { getPermalink } from 'utils/permalinks';
|
||||||
|
import { getFormattedDate } from 'utils/utils';
|
||||||
|
|
||||||
|
import type { Post } from 'types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
post: Post;
|
||||||
|
url: string | URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post, url } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="py-8 sm:py-16 lg:py-20 mx-auto">
|
||||||
|
<article>
|
||||||
|
<header class={post.image ? '' : ''}>
|
||||||
|
<div class="flex justify-between flex-col sm:flex-row max-w-3xl mx-auto mt-0 mb-2 px-4 sm:px-6 sm:items-center">
|
||||||
|
<p>
|
||||||
|
<Icon name="tabler:clock" class="w-4 h-4 inline-block -mt-0.5 dark:text-gray-400" />
|
||||||
|
<time datetime={String(post.publishDate)} class="inline-block">{getFormattedDate(post.publishDate)}</time>
|
||||||
|
{
|
||||||
|
post.author && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
· <Icon name="tabler:user" class="w-4 h-4 inline-block -mt-0.5 dark:text-gray-400" />
|
||||||
|
<span class="inline-block">{post.author}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
post.category && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
·{' '}
|
||||||
|
<a class="hover:underline inline-block" href={getPermalink(post.category.slug, 'category')}>
|
||||||
|
{post.category.title}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
post.readingTime && (
|
||||||
|
<>
|
||||||
|
· <span>{post.readingTime}</span> min read
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
class="px-4 sm:px-6 max-w-3xl mx-auto text-4xl md:text-5xl font-bold leading-tighter tracking-tighter font-heading"
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
class="max-w-3xl mx-auto mt-4 mb-8 px-4 sm:px-6 text-xl md:text-2xl text-muted dark:text-slate-400 text-justify"
|
||||||
|
>
|
||||||
|
{post.excerpt}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{
|
||||||
|
post.image ? (
|
||||||
|
<Image
|
||||||
|
src={post.image}
|
||||||
|
class="max-w-full lg:max-w-[900px] mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
|
||||||
|
widths={[400, 900]}
|
||||||
|
sizes="(max-width: 900px) 400px, 900px"
|
||||||
|
alt={post?.excerpt || ''}
|
||||||
|
width={900}
|
||||||
|
height={506}
|
||||||
|
loading="eager"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6">
|
||||||
|
<div class="border-t dark:border-slate-700" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
class="mx-auto px-6 sm:px-6 max-w-3xl prose prose-md lg:prose-xl dark:prose-invert dark:prose-headings:text-slate-300 prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-primary dark:prose-a:text-blue-400 prose-img:rounded-md prose-img:shadow-lg mt-8 prose-headings:scroll-mt-[80px] prose-li:my-0"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto px-6 sm:px-6 max-w-3xl mt-8 flex justify-between flex-col sm:flex-row">
|
||||||
|
<PostTags tags={post.tags} class="mr-5 rtl:mr-0 rtl:ml-5" />
|
||||||
|
<SocialShare url={url} text={post.title} class="mt-5 sm:mt-1 align-middle text-gray-500 dark:text-slate-600" />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
45
front/src/components/blog/Tags.astro
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
import { getPermalink } from 'utils/permalinks';
|
||||||
|
|
||||||
|
import { APP_BLOG } from 'astrowind:config';
|
||||||
|
import type { Post } from 'types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
tags: Post['tags'];
|
||||||
|
class?: string;
|
||||||
|
title?: string | undefined;
|
||||||
|
isCategory?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tags, class: className = 'text-sm', title = undefined, isCategory = false } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
tags && Array.isArray(tags) && (
|
||||||
|
<>
|
||||||
|
<>
|
||||||
|
{title !== undefined && (
|
||||||
|
<span class="align-super font-normal underline underline-offset-4 decoration-2 dark:text-slate-400">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
<ul class={className}>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<li class="bg-gray-100 dark:bg-slate-700 inline-block mr-2 rtl:mr-0 rtl:ml-2 mb-2 py-0.5 px-2 lowercase font-medium">
|
||||||
|
{!APP_BLOG?.tag?.isEnabled ? (
|
||||||
|
tag.title
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
href={getPermalink(tag.slug, isCategory ? 'category' : 'tag')}
|
||||||
|
class="text-muted dark:text-slate-300 hover:text-primary dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
|
{tag.title}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
20
front/src/components/blog/ToBlogLink.astro
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import { getBlogPermalink } from 'utils/permalinks';
|
||||||
|
import { I18N } from 'astrowind:config';
|
||||||
|
import Button from 'components/ui/Button.astro';
|
||||||
|
|
||||||
|
const { textDirection } = I18N;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="mx-auto px-6 sm:px-6 max-w-3xl pt-8 md:pt-4 pb-12 md:pb-20">
|
||||||
|
<Button variant="tertiary" class="px-3 md:px-3" href={getBlogPermalink()}>
|
||||||
|
{
|
||||||
|
textDirection === 'rtl' ? (
|
||||||
|
<Icon name="tabler:chevron-right" class="w-5 h-5 mr-1 -ml-1.5 rtl:-mr-1.5 rtl:ml-1" />
|
||||||
|
) : (
|
||||||
|
<Icon name="tabler:chevron-left" class="w-5 h-5 mr-1 -ml-1.5 rtl:-mr-1.5 rtl:ml-1" />
|
||||||
|
)
|
||||||
|
} Back to Blog
|
||||||
|
</Button>
|
||||||
|
</div>
|
13
front/src/components/common/Analytics.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { GoogleAnalytics } from '@astrolib/analytics';
|
||||||
|
import { ANALYTICS } from 'astrowind:config';
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
ANALYTICS?.vendors?.googleAnalytics?.id ? (
|
||||||
|
<GoogleAnalytics
|
||||||
|
id={String(ANALYTICS.vendors.googleAnalytics.id)}
|
||||||
|
partytown={ANALYTICS?.vendors?.googleAnalytics?.partytown}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
33
front/src/components/common/ApplyColorMode.astro
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
import { UI } from 'astrowind:config';
|
||||||
|
|
||||||
|
// TODO: This code is temporary
|
||||||
|
---
|
||||||
|
|
||||||
|
<script is:inline define:vars={{ defaultTheme: UI.theme || 'system' }}>
|
||||||
|
function applyTheme(theme) {
|
||||||
|
if (theme === 'dark') {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
const matches = document.querySelectorAll('[data-aw-toggle-color-scheme] > input');
|
||||||
|
|
||||||
|
if (matches && matches.length) {
|
||||||
|
matches.forEach((elem) => {
|
||||||
|
elem.checked = theme !== 'dark';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((defaultTheme && defaultTheme.endsWith(':only')) || (!localStorage.theme && defaultTheme !== 'system')) {
|
||||||
|
applyTheme(defaultTheme.replace(':only', ''));
|
||||||
|
} else if (
|
||||||
|
localStorage.theme === 'dark' ||
|
||||||
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
) {
|
||||||
|
applyTheme('dark');
|
||||||
|
} else {
|
||||||
|
applyTheme('light');
|
||||||
|
}
|
||||||
|
</script>
|
162
front/src/components/common/BasicScripts.astro
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
---
|
||||||
|
import { UI } from 'astrowind:config';
|
||||||
|
---
|
||||||
|
|
||||||
|
<script is:inline define:vars={{ defaultTheme: UI.theme }}>
|
||||||
|
if (window.basic_script) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.basic_script = true;
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
if (theme === 'dark') {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTheme = function () {
|
||||||
|
if ((defaultTheme && defaultTheme.endsWith(':only')) || (!localStorage.theme && defaultTheme !== 'system')) {
|
||||||
|
applyTheme(defaultTheme.replace(':only', ''));
|
||||||
|
} else if (
|
||||||
|
localStorage.theme === 'dark' ||
|
||||||
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
) {
|
||||||
|
applyTheme('dark');
|
||||||
|
} else {
|
||||||
|
applyTheme('light');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
function attachEvent(selector, event, fn) {
|
||||||
|
const matches = typeof selector === 'string' ? document.querySelectorAll(selector) : selector;
|
||||||
|
if (matches && matches.length) {
|
||||||
|
matches.forEach((elem) => {
|
||||||
|
elem.addEventListener(event, (e) => fn(e, elem), false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLoad = function () {
|
||||||
|
let lastKnownScrollPosition = window.scrollY;
|
||||||
|
let ticking = true;
|
||||||
|
|
||||||
|
attachEvent('#header nav', 'click', function () {
|
||||||
|
document.querySelector('[data-aw-toggle-menu]')?.classList.remove('expanded');
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
document.getElementById('header')?.classList.remove('h-screen');
|
||||||
|
document.getElementById('header')?.classList.remove('expanded');
|
||||||
|
document.getElementById('header')?.classList.remove('bg-page');
|
||||||
|
document.querySelector('#header nav')?.classList.add('hidden');
|
||||||
|
document.querySelector('#header > div > div:last-child')?.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
attachEvent('[data-aw-toggle-menu]', 'click', function (_, elem) {
|
||||||
|
elem.classList.toggle('expanded');
|
||||||
|
document.body.classList.toggle('overflow-hidden');
|
||||||
|
document.getElementById('header')?.classList.toggle('h-screen');
|
||||||
|
document.getElementById('header')?.classList.toggle('expanded');
|
||||||
|
document.getElementById('header')?.classList.toggle('bg-page');
|
||||||
|
document.querySelector('#header nav')?.classList.toggle('hidden');
|
||||||
|
document.querySelector('#header > div > div:last-child')?.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
attachEvent('[data-aw-toggle-color-scheme]', 'click', function () {
|
||||||
|
if (defaultTheme.endsWith(':only')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.documentElement.classList.toggle('dark');
|
||||||
|
localStorage.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
|
||||||
|
});
|
||||||
|
|
||||||
|
attachEvent('[data-aw-social-share]', 'click', function (_, elem) {
|
||||||
|
const network = elem.getAttribute('data-aw-social-share');
|
||||||
|
const url = encodeURIComponent(elem.getAttribute('data-aw-url'));
|
||||||
|
const text = encodeURIComponent(elem.getAttribute('data-aw-text'));
|
||||||
|
|
||||||
|
let href;
|
||||||
|
switch (network) {
|
||||||
|
case 'facebook':
|
||||||
|
href = `https://www.facebook.com/sharer.php?u=${url}`;
|
||||||
|
break;
|
||||||
|
case 'twitter':
|
||||||
|
href = `https://twitter.com/intent/tweet?url=${url}&text=${text}`;
|
||||||
|
break;
|
||||||
|
case 'linkedin':
|
||||||
|
href = `https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${text}`;
|
||||||
|
break;
|
||||||
|
case 'whatsapp':
|
||||||
|
href = `https://wa.me/?text=${text}%20${url}`;
|
||||||
|
break;
|
||||||
|
case 'mail':
|
||||||
|
href = `mailto:?subject=%22${text}%22&body=${text}%20${url}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newlink = document.createElement('a');
|
||||||
|
newlink.target = '_blank';
|
||||||
|
newlink.href = href;
|
||||||
|
newlink.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
const screenSize = window.matchMedia('(max-width: 767px)');
|
||||||
|
screenSize.addEventListener('change', function () {
|
||||||
|
document.querySelector('[data-aw-toggle-menu]')?.classList.remove('expanded');
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
document.getElementById('header')?.classList.remove('h-screen');
|
||||||
|
document.getElementById('header')?.classList.remove('expanded');
|
||||||
|
document.getElementById('header')?.classList.remove('bg-page');
|
||||||
|
document.querySelector('#header nav')?.classList.add('hidden');
|
||||||
|
document.querySelector('#header > div > div:last-child')?.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
function applyHeaderStylesOnScroll() {
|
||||||
|
const header = document.querySelector('#header[data-aw-sticky-header]');
|
||||||
|
if (!header) return;
|
||||||
|
if (lastKnownScrollPosition > 60 && !header.classList.contains('scroll')) {
|
||||||
|
header.classList.add('scroll');
|
||||||
|
} else if (lastKnownScrollPosition <= 60 && header.classList.contains('scroll')) {
|
||||||
|
header.classList.remove('scroll');
|
||||||
|
}
|
||||||
|
ticking = false;
|
||||||
|
}
|
||||||
|
applyHeaderStylesOnScroll();
|
||||||
|
|
||||||
|
attachEvent([document], 'scroll', function () {
|
||||||
|
lastKnownScrollPosition = window.scrollY;
|
||||||
|
|
||||||
|
if (!ticking) {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
applyHeaderStylesOnScroll();
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onPageShow = function () {
|
||||||
|
document.documentElement.classList.add('motion-safe:scroll-smooth');
|
||||||
|
const elem = document.querySelector('[data-aw-toggle-menu]');
|
||||||
|
if (elem) {
|
||||||
|
elem.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
document.getElementById('header')?.classList.remove('h-screen');
|
||||||
|
document.getElementById('header')?.classList.remove('expanded');
|
||||||
|
document.querySelector('#header nav')?.classList.add('hidden');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = onLoad;
|
||||||
|
window.onpageshow = onPageShow;
|
||||||
|
|
||||||
|
document.addEventListener('astro:after-swap', () => {
|
||||||
|
initTheme();
|
||||||
|
onLoad();
|
||||||
|
onPageShow();
|
||||||
|
});
|
||||||
|
</script>
|
8
front/src/components/common/CommonMeta.astro
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
import { getAsset } from 'utils/permalinks';
|
||||||
|
---
|
||||||
|
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<link rel="sitemap" href={getAsset('/sitemap-index.xml')} />
|
61
front/src/components/common/Image.astro
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
import { findImage } from 'utils/images';
|
||||||
|
import {
|
||||||
|
getImagesOptimized,
|
||||||
|
astroAsseetsOptimizer,
|
||||||
|
unpicOptimizer,
|
||||||
|
isUnpicCompatible,
|
||||||
|
type ImageProps,
|
||||||
|
type AttributesProps,
|
||||||
|
} from 'utils/images-optimization';
|
||||||
|
|
||||||
|
type Props = ImageProps;
|
||||||
|
type ImageType = {
|
||||||
|
src: string;
|
||||||
|
attributes: AttributesProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = Astro.props;
|
||||||
|
|
||||||
|
if (props.alt === undefined || props.alt === null) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.width === 'string') {
|
||||||
|
props.width = parseInt(props.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.height === 'string') {
|
||||||
|
props.height = parseInt(props.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.loading) {
|
||||||
|
props.loading = 'lazy';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.decoding) {
|
||||||
|
props.decoding = 'async';
|
||||||
|
}
|
||||||
|
|
||||||
|
const _image = await findImage(props.src);
|
||||||
|
|
||||||
|
let image: ImageType | undefined = undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof _image === 'string' &&
|
||||||
|
(_image.startsWith('http://') || _image.startsWith('https://')) &&
|
||||||
|
isUnpicCompatible(_image)
|
||||||
|
) {
|
||||||
|
image = await getImagesOptimized(_image, props, unpicOptimizer);
|
||||||
|
} else if (_image) {
|
||||||
|
image = await getImagesOptimized(_image, props, astroAsseetsOptimizer);
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
!image ? (
|
||||||
|
<Fragment />
|
||||||
|
) : (
|
||||||
|
<img src={image.src} crossorigin="anonymous" referrerpolicy="no-referrer" {...image.attributes} />
|
||||||
|
)
|
||||||
|
}
|
68
front/src/components/common/Metadata.astro
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
import merge from 'lodash.merge';
|
||||||
|
import { AstroSeo } from '@astrolib/seo';
|
||||||
|
|
||||||
|
import type { Props as AstroSeoProps } from '@astrolib/seo';
|
||||||
|
|
||||||
|
import { SITE, METADATA, I18N } from 'astrowind:config';
|
||||||
|
import type { MetaData } from 'types';
|
||||||
|
import { getCanonical } from 'utils/permalinks';
|
||||||
|
|
||||||
|
import { adaptOpenGraphImages } from 'utils/images';
|
||||||
|
|
||||||
|
export interface Props extends MetaData {
|
||||||
|
dontUseTitleTemplate?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
ignoreTitleTemplate = false,
|
||||||
|
canonical = String(getCanonical(String(Astro.url.pathname))),
|
||||||
|
robots = {},
|
||||||
|
description,
|
||||||
|
openGraph = {},
|
||||||
|
twitter = {},
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const seoProps: AstroSeoProps = merge(
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
titleTemplate: '%s',
|
||||||
|
canonical: canonical,
|
||||||
|
noindex: true,
|
||||||
|
nofollow: true,
|
||||||
|
description: undefined,
|
||||||
|
openGraph: {
|
||||||
|
url: canonical,
|
||||||
|
site_name: SITE?.name,
|
||||||
|
images: [],
|
||||||
|
locale: I18N?.language || 'en',
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
cardType: openGraph?.images?.length ? 'summary_large_image' : 'summary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: METADATA?.title?.default,
|
||||||
|
titleTemplate: METADATA?.title?.template,
|
||||||
|
noindex: typeof METADATA?.robots?.index !== 'undefined' ? !METADATA.robots.index : undefined,
|
||||||
|
nofollow: typeof METADATA?.robots?.follow !== 'undefined' ? !METADATA.robots.follow : undefined,
|
||||||
|
description: METADATA?.description,
|
||||||
|
openGraph: METADATA?.openGraph,
|
||||||
|
twitter: METADATA?.twitter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: title,
|
||||||
|
titleTemplate: ignoreTitleTemplate ? '%s' : undefined,
|
||||||
|
canonical: canonical,
|
||||||
|
noindex: typeof robots?.index !== 'undefined' ? !robots.index : undefined,
|
||||||
|
nofollow: typeof robots?.follow !== 'undefined' ? !robots.follow : undefined,
|
||||||
|
description: description,
|
||||||
|
openGraph: { url: canonical, ...openGraph },
|
||||||
|
twitter: twitter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<AstroSeo {...{ ...seoProps, openGraph: await adaptOpenGraphImages(seoProps?.openGraph, Astro.site) }} />
|
5
front/src/components/common/SiteVerification.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import { SITE } from 'astrowind:config';
|
||||||
|
---
|
||||||
|
|
||||||
|
{SITE.googleSiteVerificationId && <meta name="google-site-verification" content={SITE.googleSiteVerificationId} />}
|
65
front/src/components/common/SocialShare.astro
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
text: string;
|
||||||
|
url: string | URL;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text, url, class: className = 'inline-block' } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={className}>
|
||||||
|
<span class="align-super font-bold text-slate-500 dark:text-slate-400">Share:</span>
|
||||||
|
<button
|
||||||
|
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||||
|
title="Twitter Share"
|
||||||
|
data-aw-social-share="twitter"
|
||||||
|
data-aw-url={url}
|
||||||
|
data-aw-text={text}
|
||||||
|
><Icon
|
||||||
|
name="tabler:brand-x"
|
||||||
|
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button class="ml-2 rtl:ml-0 rtl:mr-2" title="Facebook Share" data-aw-social-share="facebook" data-aw-url={url}
|
||||||
|
><Icon
|
||||||
|
name="tabler:brand-facebook"
|
||||||
|
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||||
|
title="Linkedin Share"
|
||||||
|
data-aw-social-share="linkedin"
|
||||||
|
data-aw-url={url}
|
||||||
|
data-aw-text={text}
|
||||||
|
><Icon
|
||||||
|
name="tabler:brand-linkedin"
|
||||||
|
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||||
|
title="Whatsapp Share"
|
||||||
|
data-aw-social-share="whatsapp"
|
||||||
|
data-aw-url={url}
|
||||||
|
data-aw-text={text}
|
||||||
|
><Icon
|
||||||
|
name="tabler:brand-whatsapp"
|
||||||
|
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||||
|
title="Email Share"
|
||||||
|
data-aw-social-share="mail"
|
||||||
|
data-aw-url={url}
|
||||||
|
data-aw-text={text}
|
||||||
|
><Icon
|
||||||
|
name="tabler:mail"
|
||||||
|
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
6
front/src/components/common/SplitbeeAnalytics.astro
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
const { doNotTrack = true, noCookieMode = false, url = 'https://cdn.splitbee.io/sb.js' } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Splitbee Analytics -->
|
||||||
|
<script is:inline data-respect-dnt={doNotTrack} data-no-cookie={noCookieMode} async src={url}></script>
|
29
front/src/components/common/ToggleMenu.astro
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
label?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
label = 'Toggle Menu',
|
||||||
|
class: className = 'flex flex-col h-12 w-12 rounded justify-center items-center cursor-pointer group',
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<button type="button" class={className} aria-label={label} data-aw-toggle-menu>
|
||||||
|
<span class="sr-only">{label}</span>
|
||||||
|
<slot>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-0.5 w-6 my-1 rounded-full bg-black dark:bg-white transition ease transform duration-200 opacity-80 group-[.expanded]:rotate-45 group-[.expanded]:translate-y-2.5"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-0.5 w-6 my-1 rounded-full bg-black dark:bg-white transition ease transform duration-200 opacity-80 group-[.expanded]:opacity-0"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-0.5 w-6 my-1 rounded-full bg-black dark:bg-white transition ease transform duration-200 opacity-80 group-[.expanded]:-rotate-45 group-[.expanded]:-translate-y-2.5"
|
||||||
|
></span>
|
||||||
|
</slot>
|
||||||
|
</button>
|
28
front/src/components/common/ToggleTheme.astro
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
|
import { UI } from 'astrowind:config';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
label?: string;
|
||||||
|
class?: string;
|
||||||
|
iconClass?: string;
|
||||||
|
iconName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
label = 'Toggle between Dark and Light mode',
|
||||||
|
class:
|
||||||
|
className = 'text-muted dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center',
|
||||||
|
iconClass = 'w-6 h-6',
|
||||||
|
iconName = 'tabler:sun',
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
!(UI.theme && UI.theme.endsWith(':only')) && (
|
||||||
|
<button type="button" class={className} aria-label={label} data-aw-toggle-color-scheme>
|
||||||
|
<Icon name={iconName} class={iconClass} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|