Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
Francesco Minnocci | 12687ba08c | 2 years ago |
Antonio De Lucreziis | 1430c57423 | 3 years ago |
Antonio De Lucreziis | 1682cdca81 | 3 years ago |
Francesco Minnocci | 7387307ad9 | 3 years ago |
Francesco Minnocci | 6d4c742d4c | 3 years ago |
Antonio De Lucreziis | 3d10f70d71 | 3 years ago |
Antonio De Lucreziis | 44ffecbdd2 | 3 years ago |
Francesco Minnocci | f670c78337 | 3 years ago |
Antonio De Lucreziis | d5aa7c0ba2 | 3 years ago |
Antonio De Lucreziis | 2a76e0621b | 3 years ago |
Antonio De Lucreziis | 35d42b8316 | 3 years ago |
Antonio De Lucreziis | 10704d946f | 3 years ago |
Antonio De Lucreziis | ff0e0a5dcc | 3 years ago |
Antonio De Lucreziis | 1495a5e45b | 3 years ago |
Antonio De Lucreziis | a970cea91a | 3 years ago |
Antonio De Lucreziis | fb2db34a9b | 3 years ago |
@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Posti DM</title>
|
||||
|
||||
<link href="https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div class="nav-group left">
|
||||
<div class="nav-item" id="clock">14:23</div>
|
||||
</div>
|
||||
<div class="nav-group center">
|
||||
<div class="nav-item">
|
||||
<a href="/">Posti DM</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/stanze">Stanze</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group right">
|
||||
<div id="login-label" class="nav-item">
|
||||
<a href="/login">Login</a>
|
||||
</div>
|
||||
<div id="logged-label" class="nav-item hidden">
|
||||
<!-- @username -->
|
||||
</div>
|
||||
<div id="logout-label" class="nav-item hidden">
|
||||
<button id="logout-button">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main>
|
||||
<div id="orari-time-table" class="time-table">
|
||||
<div class="controls">
|
||||
<div class="current-range">Settimana, 21 Marzo — 27 Marzo</div>
|
||||
<div class="group">
|
||||
<div class="previous">
|
||||
<button class="square">
|
||||
<i class="bx bx-chevron-left bx-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="today">
|
||||
<button class="square">
|
||||
<i class="bx bxs-home"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="next">
|
||||
<button class="square">
|
||||
<i class="bx bx-chevron-right bx-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="weekly">
|
||||
<div class="day">
|
||||
<div class="title">Lun 21</div>
|
||||
<div class="slot">
|
||||
<div class="range">8:30 – 11:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">11:00 – 13:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">13:30 – 16:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">16:00 – 19:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="day">
|
||||
<div class="title">Mar 21</div>
|
||||
<div class="slot">
|
||||
<div class="range">8:30 – 11:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">11:00 – 13:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">13:30 – 16:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">16:00 – 19:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="day">
|
||||
<div class="title">Mer 21</div>
|
||||
<div class="slot">
|
||||
<div class="range">8:30 – 11:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">11:00 – 13:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">13:30 – 16:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">16:00 – 19:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="day">
|
||||
<div class="title">Gio 21</div>
|
||||
<div class="slot">
|
||||
<div class="range">8:30 – 11:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">11:00 – 13:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">13:30 – 16:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">16:00 – 19:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="day">
|
||||
<div class="title">Ven 21</div>
|
||||
<div class="slot">
|
||||
<div class="range">8:30 – 11:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">11:00 – 13:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">13:30 – 16:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">16:00 – 19:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="day">
|
||||
<div class="title">Sab 21</div>
|
||||
<div class="slot">
|
||||
<div class="range">8:30 – 11:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">11:00 – 13:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">13:30 – 16:00</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<div class="range">16:00 – 19:30</div>
|
||||
<div class="counter">100/100</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script type="module" src="./src/pages/orari.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Posti DM</title>
|
||||
|
||||
<link href="https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div class="nav-group left">
|
||||
<div class="nav-item" id="clock">14:23</div>
|
||||
</div>
|
||||
<div class="nav-group center">
|
||||
<div class="nav-item">
|
||||
<a href="/">Posti DM</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/stanze">Stanze</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a href="/orari">Orari</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group right">
|
||||
<div id="login-label" class="nav-item">
|
||||
<a href="/login">Login</a>
|
||||
</div>
|
||||
<div id="logged-label" class="nav-item hidden">
|
||||
<!-- @username -->
|
||||
</div>
|
||||
<div id="logout-label" class="nav-item hidden">
|
||||
<button id="logout-button">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main>
|
||||
<p>Prenota</p>
|
||||
</main>
|
||||
<script type="module" src="./src/pages/prenota.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,117 @@
|
||||
import './style.scss'
|
||||
|
||||
/**
|
||||
* `BASE_URL` string from environment file without final slash (for more readable interpolated strings)
|
||||
*/
|
||||
export const BASE_URL = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||||
|
||||
//
|
||||
// User
|
||||
//
|
||||
|
||||
const NotCached = Symbol('not cached')
|
||||
let userSession = NotCached
|
||||
|
||||
export const User = {
|
||||
async getLogged() {
|
||||
if (userSession === NotCached) {
|
||||
console.log('Caching user data...')
|
||||
|
||||
const res = await fetch(`${BASE_URL}/api/user`)
|
||||
const user = await res.json()
|
||||
|
||||
userSession = user
|
||||
}
|
||||
|
||||
return userSession
|
||||
},
|
||||
async login(username, password) {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: await response.text() }
|
||||
}
|
||||
|
||||
// If successful redirect to homepage
|
||||
location.href = `${BASE_URL}/`
|
||||
} catch (e) {
|
||||
return { error: e }
|
||||
}
|
||||
},
|
||||
async logout() {
|
||||
await fetch(`${BASE_URL}/api/logout`, { method: 'POST' })
|
||||
location.href = `${BASE_URL}/`
|
||||
},
|
||||
}
|
||||
|
||||
//
|
||||
// Room Event Source
|
||||
//
|
||||
|
||||
export function createRoomEventStream(roomId) {
|
||||
return new EventSource(`${BASE_URL}/api/room_events?id=${roomId}`)
|
||||
}
|
||||
|
||||
//
|
||||
// Database
|
||||
//
|
||||
|
||||
export const Database = {
|
||||
async getSeats(roomId) {
|
||||
const seatList = await (await fetch(`${BASE_URL}/api/room/seats?id=${roomId}`)).json()
|
||||
const seats = {}
|
||||
|
||||
seatList.forEach(seat => {
|
||||
seats[seat.id] = seat
|
||||
})
|
||||
|
||||
return seats
|
||||
},
|
||||
async occupySeat(seatId) {
|
||||
const response = await fetch(`${BASE_URL}/api/seat/occupy?id=${seatId}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text())
|
||||
}
|
||||
|
||||
await response.json()
|
||||
},
|
||||
async leaveSeat(seatId) {
|
||||
const response = await fetch(`${BASE_URL}/api/seat/leave?id=${seatId}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text())
|
||||
}
|
||||
|
||||
await response.json()
|
||||
},
|
||||
}
|
||||
|
||||
//
|
||||
// Fix page links
|
||||
//
|
||||
|
||||
// TODO: It might actually be better to just create a <page-link> custom element
|
||||
// Always append BASE_URL to all <a> links in the page (in development mode links should point to html files)
|
||||
document.querySelectorAll('a').forEach($a => {
|
||||
const url = $a.getAttribute('href')
|
||||
let newUrl = BASE_URL + url
|
||||
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
newUrl += newUrl.endsWith('/') ? 'index.html' : '.html'
|
||||
}
|
||||
|
||||
$a.href = newUrl
|
||||
})
|
@ -0,0 +1,40 @@
|
||||
import { User } from '../common.js'
|
||||
|
||||
function elements(el) {
|
||||
return {
|
||||
elLoggedLabel: el.querySelector('.logged-label'),
|
||||
elLoginLabel: el.querySelector('.login-label'),
|
||||
elLogoutLabel: el.querySelector('.logout-label'),
|
||||
elLogoutButton: el.querySelector('.logout-button'),
|
||||
}
|
||||
}
|
||||
|
||||
function setup(el) {
|
||||
const { elLogoutButton } = elements(el)
|
||||
|
||||
elLogoutButton.addEventListener('click', () => User.logout())
|
||||
}
|
||||
|
||||
function update(el, { user }) {
|
||||
const { elLoggedLabel, elLoginLabel, elLogoutLabel } = elements(el)
|
||||
|
||||
elLoginLabel.classList.toggle('hidden', user)
|
||||
|
||||
const roleString =
|
||||
user && user.permissions.length > 0 ? ` (${user.permissions.join(', ')})` : ''
|
||||
|
||||
elLoggedLabel.innerText = user ? `@${user.id}${roleString}` : ''
|
||||
|
||||
elLoggedLabel.classList.toggle('hidden', !user)
|
||||
elLogoutLabel.classList.toggle('hidden', !user)
|
||||
}
|
||||
|
||||
export function createNavUser(el) {
|
||||
setup(el)
|
||||
|
||||
// Load current user
|
||||
;(async () => {
|
||||
const user = await User.getLogged()
|
||||
update(el, { user })
|
||||
})()
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import { BASE_URL, Database } from '../common'
|
||||
|
||||
export function createTimeTable(el) {}
|
@ -1,52 +0,0 @@
|
||||
import './style.scss'
|
||||
|
||||
export const BASE_URL = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||||
|
||||
let USER = false
|
||||
export async function getLoggedUser() {
|
||||
if (USER === false) {
|
||||
console.log('Caching user data...')
|
||||
USER = await (await fetch(`${BASE_URL}/api/user`)).json()
|
||||
}
|
||||
|
||||
return USER
|
||||
}
|
||||
|
||||
export function createRoomEventStream(roomId) {
|
||||
return new EventSource(`${BASE_URL}/api/room_events?id=${roomId}`)
|
||||
}
|
||||
|
||||
export const Database = {
|
||||
async getSeats(roomId) {
|
||||
const seatList = await (await fetch(`${BASE_URL}/api/room/seats?id=${roomId}`)).json()
|
||||
const seats = {}
|
||||
|
||||
seatList.forEach(seat => {
|
||||
seats[seat.id] = seat
|
||||
})
|
||||
|
||||
return seats
|
||||
},
|
||||
async occupySeat(seatId) {
|
||||
const response = await fetch(`${BASE_URL}/api/seat/occupy?id=${seatId}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text())
|
||||
}
|
||||
|
||||
await response.json()
|
||||
},
|
||||
async leaveSeat(seatId) {
|
||||
const response = await fetch(`${BASE_URL}/api/seat/leave?id=${seatId}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text())
|
||||
}
|
||||
|
||||
await response.json()
|
||||
},
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import '../common'
|
||||
import { createTimeTable } from '../components/time-table.js'
|
||||
|
||||
const elTimeTable = document.querySelector('#orari-time-table')
|
||||
|
||||
async function main() {
|
||||
createTimeTable(elTimeTable)
|
||||
}
|
||||
|
||||
main()
|
@ -0,0 +1,7 @@
|
||||
import '../common.js'
|
||||
|
||||
async function main() {
|
||||
// TODO: ...
|
||||
}
|
||||
|
||||
main()
|
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Prelude
|
||||
//
|
||||
|
||||
type Natural = number
|
||||
type Integer = number
|
||||
type Rational = [number, number]
|
||||
type Real = number
|
||||
|
||||
type Datetime = string
|
||||
type Time = string
|
||||
|
||||
type Maybe<T> = { present: false } | { present: true; value: T }
|
||||
|
||||
//
|
||||
// PostiDM
|
||||
//
|
||||
|
||||
type UserID = string
|
||||
type BookingID = string
|
||||
type SlotID = string
|
||||
type SeatID = string
|
||||
type RoomID = string
|
||||
|
||||
type UserPermission = 'basic' | 'helper' | 'moderator' | 'admin'
|
||||
|
||||
type PostiDM = {
|
||||
users: Map<UserID, User>
|
||||
rooms: Map<RoomID, Room>
|
||||
seats: Map<SeatID, Seat>
|
||||
|
||||
slots: Map<SlotID, Slot>
|
||||
getCurrentWeekSlots(): Slot[]
|
||||
|
||||
bookings: Map<BookingID, Booking>
|
||||
}
|
||||
|
||||
// TODO: Tutte le funzioni "getCurrentSomething()" in realtà sono da pensare meglio in modo da poter passare un range temporale o qualcosa del genere
|
||||
|
||||
/**
|
||||
* Un utente loggato con credenziali di ateneo
|
||||
*/
|
||||
type User = {
|
||||
id: UserID
|
||||
permissions: Set<UserPermission>
|
||||
|
||||
getBookings(): Booking[]
|
||||
getCurrentSeat(): Maybe<Seat>
|
||||
}
|
||||
|
||||
/**
|
||||
* Una prenotazione di un utente per un certo slot orario
|
||||
*/
|
||||
type Booking = {
|
||||
id: BookingID
|
||||
timestamp: Datetime
|
||||
slotID: SlotID
|
||||
userID: UserID // cioè ok c'è User.bookings però pensando in SQL è sempre meglio avere comunque un id qui (forse)
|
||||
|
||||
getSlot(): Slot // giusto per comodità per parlare direttamente dell'oggetto "Slot" relativo ad un "Booking"
|
||||
getSeat(): Seat // ".getSlot().getSeat()"
|
||||
}
|
||||
|
||||
/**
|
||||
* Slot rappresenta uno slot orario prenotabile per un certo posto
|
||||
*/
|
||||
type Slot = {
|
||||
id: SlotID
|
||||
seatID: SeatID
|
||||
// Magari invece di eliminare uno slot, in quanto ricrearlo potrebbe essere complicato e forse è meglio che i moderatori semplicemente disabilitino uno slot se vogliono non renderlo prenotabile (?)
|
||||
// disabled: boolean
|
||||
range: {
|
||||
from: Datetime
|
||||
to: Datetime
|
||||
}
|
||||
|
||||
getCurrentBooking(): Maybe<Booking>
|
||||
}
|
||||
|
||||
/**
|
||||
* Seat rappresenta un posto in dipartimento in una certa stanza
|
||||
*/
|
||||
type Seat = {
|
||||
id: SeatID
|
||||
diagram: {
|
||||
x: Natural
|
||||
y: Natural
|
||||
width: Natural
|
||||
height: Natural
|
||||
}
|
||||
|
||||
// TODO: Forse per ora è meglio fare che più utenti possono prenotare lo stesso posto così inizialmente possiamo fare che in ogni stanza c'è solo un posto e la gente può prenotarsi solo "alla stanza" e non al posto specifico, altrimenti facciamo "getCurrentBookedUser(): Maybe<User>"
|
||||
getCurrentlyBookedUsers(): User[]
|
||||
getRoom(): Room
|
||||
}
|
||||
|
||||
/**
|
||||
* Room rappresenta una stanza in dipartimento e contiene dei posti
|
||||
*/
|
||||
type Room = {
|
||||
id: RoomID
|
||||
name: string
|
||||
diagram: {
|
||||
gridRows: Natural
|
||||
gridCols: Natural
|
||||
}
|
||||
|
||||
getSeats(): Seat[]
|
||||
getTotalSeatCount(): Natural // ".seatIDs.length"
|
||||
getCurrentBookedSeatCount(): Natural
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.phc.dm.unipi.it/aziis98/posti-dm/server/auth"
|
||||
"git.phc.dm.unipi.it/aziis98/posti-dm/server/db"
|
||||
"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
|
||||
)
|
||||
|
||||
// simpleAuthenticator holds an in memory map of session tokens and a reference to the main database interface
|
||||
type simpleAuthenticator struct {
|
||||
// sessions is a map from a sessionToken to userID
|
||||
sessions map[string]string
|
||||
database db.Database
|
||||
}
|
||||
|
||||
func (service *simpleAuthenticator) CheckUserPassword(userID, password string) error {
|
||||
if password != "phc" {
|
||||
return fmt.Errorf(`invalid password`)
|
||||
}
|
||||
|
||||
// FIXME: al momento quando la password è giusta creiamo tutti gli account necessari
|
||||
err := service.database.CreateUser(&db.User{
|
||||
ID: userID,
|
||||
Permissions: make(util.Set[string]),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf(`got "%v" while trying to log as @%s`, err, userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *simpleAuthenticator) UserPermissions(userID string) ([]string, error) {
|
||||
user, err := service.database.GetUser(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user.Permissions.Elements(), nil
|
||||
}
|
||||
|
||||
func (service *simpleAuthenticator) SessionTokenFromUser(userID string) (string, error) {
|
||||
user, err := service.database.GetUser(userID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := util.RandomHash(20)
|
||||
service.sessions[token] = user.ID
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (service *simpleAuthenticator) UserFromSessionToken(session string) (*db.User, error) {
|
||||
userID, present := service.sessions[session]
|
||||
if !present {
|
||||
return nil, auth.ErrNoUserForSession
|
||||
}
|
||||
|
||||
user, err := service.database.GetUser(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (service *simpleAuthenticator) AuthenticationFailed(err error) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
})
|
||||
}
|
||||
|
||||
func (service *simpleAuthenticator) OtherError(err error) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue