Compare commits
32 Commits
master
...
test--test
Author | SHA1 | Date | |
---|---|---|---|
ef6fe94634 | |||
e1c6f0d281 | |||
0a85c94b03 | |||
feef7c4cf1 | |||
e3acfe1a92 | |||
6c93091109 | |||
bf94c5b262 | |||
0fda82da7a | |||
5df15bc219 | |||
dc0f478c38 | |||
18dd40fbb5 | |||
b857f54115 | |||
9ab3c401a6 | |||
6e557f159a | |||
c238c547b8 | |||
848ccbf5b4 | |||
2d2bb81b93 | |||
801ab51150 | |||
120ed8182a | |||
94b2ce2d16 | |||
04dfb78d57 | |||
9a098e1433 | |||
231af73f96 | |||
9c073640f3 | |||
708fc53e4e | |||
ad549e4e53 | |||
f1525ff55a | |||
93d45c77e0 | |||
636d71c852 | |||
13872bda29 | |||
27f4228862 | |||
c108e2c955 |
8
.github/workflows/build_docker_astro.yml
vendored
@ -6,9 +6,9 @@ on: #declancheur
|
||||
- '*'
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
jobs:
|
||||
run: #jobs ID (nom du jobs)
|
||||
@ -32,6 +32,8 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern=latest
|
||||
labels: |
|
||||
org.opencontainers.image.title=AstroFront
|
||||
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
|
6
.github/workflows/build_docker_express.yml
vendored
@ -32,6 +32,8 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern=latest
|
||||
labels: |
|
||||
org.opencontainers.image.title=AstroFront
|
||||
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
@ -46,8 +48,8 @@ jobs:
|
||||
- name: Build and push back
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./Express/barAndCafe
|
||||
context: ./front
|
||||
push: true
|
||||
file: ./Express/barAndCafe/Dockerfile
|
||||
file: ./front/Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
46
.github/workflows/build_jsdoc_drink.yml
vendored
@ -1,46 +0,0 @@
|
||||
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 }}
|
2
.github/workflows/run_back_test.yml
vendored
@ -17,5 +17,3 @@ jobs:
|
||||
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}}
|
@ -1,3 +1,5 @@
|
||||
# This Dockerfile allows you to run AstroJS in server mode
|
||||
|
||||
#########
|
||||
# Build #
|
||||
#########
|
||||
@ -47,8 +49,7 @@ WORKDIR /home/node
|
||||
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
|
||||
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
|
||||
|
@ -1,35 +0,0 @@
|
||||
#########
|
||||
# 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/
|
@ -1,9 +1,5 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
testPathIgnorePatterns: [
|
||||
"<rootDir>/dist/"
|
||||
],
|
||||
testTimeout: 10000,
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
collectCoverage: true,
|
||||
@ -14,7 +10,6 @@ module.exports = {
|
||||
'!/jest.config.js/',
|
||||
'!/coverage/',
|
||||
'!**/server.ts',
|
||||
'!**/swaggerDef.ts'
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
|
@ -3,18 +3,13 @@
|
||||
"template": "node_modules/better-docs"
|
||||
},
|
||||
"tags": {
|
||||
"allowUnknownTags": true
|
||||
"allowUnknownTags": ["optional"]
|
||||
},
|
||||
"plugins": [
|
||||
"node_modules/better-docs/typescript",
|
||||
"plugins/markdown",
|
||||
"jsdoc-mermaid"
|
||||
"node_modules/better-docs/typescript"
|
||||
],
|
||||
"source": {
|
||||
"include": ["./src"],
|
||||
"includePattern": "\\.(jsx|js|ts|tsx)$"
|
||||
},
|
||||
"templates": {
|
||||
"search": true
|
||||
}
|
||||
}
|
1406
Express/barAndCafe/package-lock.json
generated
@ -2,42 +2,31 @@
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"build": "rimraf dist && npx tsc",
|
||||
"start": "node dist/src/server.js",
|
||||
"start": "node dist/index.js",
|
||||
"predev": "npm run build",
|
||||
"dev": "npx tsc -w & nodemon dist/src/server.js",
|
||||
"predocs": "npm run build",
|
||||
"dev": "npx tsc -w & nodemon dist/server.js",
|
||||
"docs": "jsdoc -c jsdoc.json",
|
||||
"pretest": "npm run build",
|
||||
"test": "ts-node -O '{\"module\":\"commonjs\"}' node_modules/jest/bin/jest.js"
|
||||
"test": "jest"
|
||||
},
|
||||
"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"
|
||||
"rimraf": "^5.0.5"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
@ -1,169 +1,22 @@
|
||||
import { getCity, getRadius, getPoiId, getBox } from "./openTripMaps"
|
||||
import express from "express"
|
||||
import cors from "cors"
|
||||
import express from "express";
|
||||
/**
|
||||
* Initialize Express application instance.
|
||||
* @returns An initialized Express application object.
|
||||
*/
|
||||
const app = express()
|
||||
const app = express();
|
||||
|
||||
app.use(cors())
|
||||
|
||||
|
||||
/**
|
||||
* Handle GET request for homepage route ('/welcome').
|
||||
* Send back a simple json response.
|
||||
* Handle GET request for homepage route ('/').
|
||||
* Send back a simple text 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)
|
||||
function getRoot(req: express.Request, res: express.Response) {
|
||||
res.send("hello world from dev");
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /welcome:
|
||||
* get:
|
||||
* summary: retun just hello
|
||||
* description: Welcome to swagger-jsdoc!
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Returns a welcome json.
|
||||
*/
|
||||
app.get("/welcome", getWelcome)
|
||||
app.get("/", getRoot);
|
||||
|
||||
/**
|
||||
* @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
|
||||
export default app;
|
@ -1,198 +0,0 @@
|
||||
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))
|
||||
}
|
||||
|
@ -1,16 +1,10 @@
|
||||
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}`)
|
||||
})
|
@ -1,29 +0,0 @@
|
||||
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
|
@ -1,30 +1,37 @@
|
||||
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')
|
||||
const port = 3000;
|
||||
|
||||
let serveur : Server<typeof IncomingMessage, typeof ServerResponse>
|
||||
|
||||
describe("Test the welcome path", () => {
|
||||
describe("Test the root path", () => {
|
||||
|
||||
test("It should response the 200 code for GET method", done => {
|
||||
beforeEach(() => {
|
||||
serveur = app.listen(port, () =>{
|
||||
console.log(`serveur running in ${port}`)
|
||||
})
|
||||
})
|
||||
|
||||
afterEach((done) => {
|
||||
serveur.close(done)
|
||||
})
|
||||
|
||||
test("It should response the GET method", done => {
|
||||
request(app)
|
||||
.get("/welcome")
|
||||
.get("/")
|
||||
.then(response => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should response the GET method with content", done => {
|
||||
const out = {hello:"world"};
|
||||
test("It should response the GET method", done => {
|
||||
request(app)
|
||||
.get("/welcome")
|
||||
.get("/")
|
||||
.then(response => {
|
||||
expect(response.text).toEqual(JSON.stringify(out))
|
||||
expect(response.text).toEqual("hello world from dev")
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -1,280 +0,0 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "Ratrapage_WEB",
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"name": "barAndCafe",
|
||||
"path": "Express/barAndCafe"
|
||||
},
|
||||
{
|
||||
"path": "front"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
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,9 +1,3 @@
|
||||
vars {
|
||||
TRIPMAP_URL: https://api.opentripmap.com/0.1
|
||||
EXPRESS_API: http://localhost:3001
|
||||
}
|
||||
vars:secret [
|
||||
app_key,
|
||||
OTM_KEY,
|
||||
GOOGLE_API_KEY
|
||||
app_key
|
||||
]
|
||||
|
@ -1,8 +0,0 @@
|
||||
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
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: express Drink OTM
|
||||
name: express test
|
||||
type: http
|
||||
seq: 4
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
@ -1,20 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
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}}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
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"
|
||||
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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}}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
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}}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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 b3bc1fdc68a325905f6d466e860732466d8e750a
|
||||
Subproject commit fb594bfd7c715a891f076750403e3a439b804468
|
@ -1,14 +1,12 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
|
||||
pocketbase:
|
||||
image: ghcr.io/coollabsio/pocketbase:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_POCKETBASE_8080
|
||||
volumes:
|
||||
- ./pb/pocketbase-data:/app/pb_data
|
||||
- ./pb/pocketbase-hooks:/app/pb_hooks
|
||||
- ./.pb/pocketbase-data:/app/pb_data
|
||||
- ./.pb/pocketbase-hooks:/app/pb_hooks
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.poketBaseTweb.rule=Host(`${POCKET_BASE_URL}`)
|
||||
@ -16,11 +14,8 @@ services:
|
||||
- traefik.http.routers.poketBaseTweb.tls.certresolver=le
|
||||
networks:
|
||||
- public
|
||||
|
||||
front:
|
||||
image: git.lab-ouest.org/epitech/ratrapage_t-web_front:pr-16-head
|
||||
environment:
|
||||
- POCKETBASE_URL=https://${POCKET_BASE_URL}
|
||||
image: git.lab-ouest.org/epitech/ratrapage_t-web_front:pr-7-head
|
||||
depends_on:
|
||||
- pocketbase
|
||||
labels:
|
||||
@ -30,69 +25,10 @@ services:
|
||||
- traefik.http.routers.astroTweb.tls.certresolver=le
|
||||
networks:
|
||||
- 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:
|
||||
graphQL: {}
|
||||
public:
|
||||
external: true
|
||||
x-dockge:
|
||||
urls:
|
||||
- https://${POCKET_BASE_URL}/
|
||||
- https://${FRONT_URL}/
|
||||
- https://${DOCS_DRINK_URL}/
|
||||
- https://${DRINK_URL}/
|
||||
- https://${DATA_TOURISME_URL}/
|
||||
|
@ -1,12 +0,0 @@
|
||||
# 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
|
@ -1,8 +0,0 @@
|
||||
node_modules
|
||||
|
||||
out
|
||||
.next
|
||||
|
||||
next-env.d.ts
|
||||
*.js
|
||||
__tests__
|
@ -1,324 +0,0 @@
|
||||
{
|
||||
"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,6 +1,8 @@
|
||||
# build output
|
||||
dist/
|
||||
.output/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
@ -11,7 +13,6 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
@ -19,6 +20,5 @@ pnpm-debug.log*
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
pnpm-lock.yaml
|
||||
|
||||
.astro
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
@ -1,2 +0,0 @@
|
||||
# Expose Astro dependencies for `pnpm` users
|
||||
shamefully-hoist=true
|
@ -1,4 +0,0 @@
|
||||
dist
|
||||
node_modules
|
||||
.github
|
||||
.changeset
|
@ -1,13 +0,0 @@
|
||||
/** @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' } }],
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"startCommand": "npm start",
|
||||
"env": {
|
||||
"ENABLE_CJS_IMPORTS": true
|
||||
}
|
||||
}
|
275
front/.vscode/astrowind/config-schema.json
vendored
@ -1,275 +0,0 @@
|
||||
{
|
||||
"$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,10 +1,4 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"astro-build.astro-vscode",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"unifiedjs.vscode-mdx"
|
||||
],
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
|
22
front/.vscode/settings.json
vendored
@ -1,22 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
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.
|
303
front/README.md
@ -1,295 +1,54 @@
|
||||
# 🚀 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:
|
||||
# Astro Starter Kit: Basics
|
||||
|
||||
```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/
|
||||
│ ├── _headers
|
||||
│ └── robots.txt
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── assets/
|
||||
│ │ ├── favicons/
|
||||
│ │ ├── images/
|
||||
│ │ └── styles/
|
||||
│ │ └── tailwind.css
|
||||
│ ├── components/
|
||||
│ │ ├── blog/
|
||||
│ │ ├── common/
|
||||
│ │ ├── ui/
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── Header.astro
|
||||
│ │ │ └── ...
|
||||
│ │ ├── CustomStyles.astro
|
||||
│ │ ├── Favicons.astro
|
||||
│ │ └── Logo.astro
|
||||
│ ├── content/
|
||||
│ │ ├── post/
|
||||
│ │ │ ├── post-slug-1.md
|
||||
│ │ │ ├── post-slug-2.mdx
|
||||
│ │ │ └── ...
|
||||
│ │ └-- config.ts
|
||||
│ │ └── Card.astro
|
||||
│ ├── layouts/
|
||||
│ │ ├── Layout.astro
|
||||
│ │ ├── MarkdownLayout.astro
|
||||
│ │ └── PageLayout.astro
|
||||
│ ├── 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
|
||||
└── ...
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
[](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
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :-------------------- | :------------------------------------------------- |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run format` | Format codes with Prettier |
|
||||
| `npm run lint:eslint` | Run Eslint |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
<br>
|
||||
## 👀 Want to learn more?
|
||||
|
||||
### 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.
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
|
@ -1,33 +1,12 @@
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import node from '@astrojs/node'
|
||||
import tailwind from '@astrojs/tailwind'
|
||||
import { defineConfig, squooshImageService } from 'astro/config';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import node from '@astrojs/node';
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
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()]) : [];
|
||||
import react from "@astrojs/react";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
|
||||
output: 'server',
|
||||
// integrations: [tailwind(), test, routing(), version(), buildInfos()],
|
||||
compressHTML: true,
|
||||
build: {
|
||||
assets: 'assets',
|
||||
@ -38,61 +17,14 @@ export default defineConfig({
|
||||
port: 3000
|
||||
},
|
||||
trailingSlash: 'never',
|
||||
output: 'server',
|
||||
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],
|
||||
},
|
||||
|
||||
integrations: [tailwind(), react()],
|
||||
vite: {
|
||||
optimizeDeps: {
|
||||
include: ['leaflet']
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
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'],
|
||||
},
|
||||
];
|
@ -1,9 +0,0 @@
|
||||
[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"
|
8383
front/package-lock.json
generated
@ -1,11 +1,8 @@
|
||||
{
|
||||
"name": "front",
|
||||
"version": "1.0.0-beta.34",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^18.17.1 || ^20.3.0 || >= 21.0.0"
|
||||
},
|
||||
"version": "0.0.1",
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "node ./dist/server/entry.mjs",
|
||||
@ -16,64 +13,32 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^8.2.5",
|
||||
"@astrojs/rss": "^4.0.5",
|
||||
"@astrojs/sitemap": "^3.1.4",
|
||||
"@astrojs/react": "^3.3.1",
|
||||
"@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",
|
||||
"astro": "^4.8.3",
|
||||
"astro-embed": "^0.7.2",
|
||||
"astro-icon": "^1.1.0",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"astro": "4.5.12",
|
||||
"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",
|
||||
"pocketbase": "^0.21.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"simple-icons-astro": "^11.12.0",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"unpic": "^3.18.0"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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-routing-machine": "^3.2.8",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/node": "^20",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitest/coverage-v8": "^1",
|
||||
"astro-eslint-parser": "^1.0.2",
|
||||
"eslint": "^8.57.0",
|
||||
"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-eslint": "^7.9.0",
|
||||
"vitest": "^1"
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
/_astro/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
@ -1,29 +0,0 @@
|
||||
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' }
|
@ -1,14 +0,0 @@
|
||||
<!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>
|
@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"infiniteLoopProtection": true,
|
||||
"hardReloadOnChange": false,
|
||||
"view": "browser",
|
||||
"template": "node",
|
||||
"container": {
|
||||
"port": 3000,
|
||||
"startScript": "start",
|
||||
"node": "18"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,9 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 749 B |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 563 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 539 KiB |
@ -1,92 +0,0 @@
|
||||
@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;
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
---
|
||||
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>
|
||||
)
|
||||
}
|
||||
</>
|
@ -1,63 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
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} />
|
@ -1,28 +0,0 @@
|
||||
---
|
||||
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>
|
||||
)
|
||||
}
|
||||
</>
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,53 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,19 +0,0 @@
|
||||
---
|
||||
|
||||
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>
|
||||
</>
|
@ -1,14 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,69 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,12 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,20 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,118 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,36 +0,0 @@
|
||||
---
|
||||
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>
|
||||
)
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
---
|
||||
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
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,45 +0,0 @@
|
||||
---
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,13 +0,0 @@
|
||||
---
|
||||
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
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,162 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
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')} />
|
@ -1,61 +0,0 @@
|
||||
---
|
||||
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} />
|
||||
)
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
---
|
||||
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) }} />
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
import { SITE } from 'astrowind:config';
|
||||
---
|
||||
|
||||
{SITE.googleSiteVerificationId && <meta name="google-site-verification" content={SITE.googleSiteVerificationId} />}
|
@ -1,65 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,29 +0,0 @@
|
||||
---
|
||||
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>
|
@ -1,28 +0,0 @@
|
||||
---
|
||||
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>
|
||||
)
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
isDark?: boolean;
|
||||
}
|
||||
|
||||
const { isDark = false } = Astro.props;
|
||||
---
|
||||
|
||||
<div class:list={['absolute inset-0', { 'bg-dark dark:bg-transparent': isDark }]}>
|
||||
<slot />
|
||||
</div>
|