Первый коммит.
This commit is contained in:
commit
f8b8e8c02b
6 changed files with 961 additions and 0 deletions
34
404.html
Normal file
34
404.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2023 Nikita Osokin.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>:(</h1>
|
||||||
|
<p>Страница не найдена.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
20
COPYRIGHT
Normal file
20
COPYRIGHT
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2023 Nikita Osokin.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
247
editpage.html
Normal file
247
editpage.html
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2023 Nikita Osokin.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<input type="date" id="date" />
|
||||||
|
<p><p/>
|
||||||
|
|
||||||
|
<table id="schedule" border=1>
|
||||||
|
<tbody>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
1.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
2.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
3.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
4.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
5.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
6.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="schedRow">
|
||||||
|
<td>
|
||||||
|
7.
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
<input id="password" type="password" placeholder="Пароль" />
|
||||||
|
<input id="send" type="button" onclick="sendEdits()" value="Отправить изменения" />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function numToUint8Array(num, arr, off) {
|
||||||
|
for(var i = 0; i < 8; i++) {
|
||||||
|
arr[off + 7 - i] = num % 0x100;
|
||||||
|
num >>= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayStatus(response) {
|
||||||
|
let sendButton = document.getElementById("send");
|
||||||
|
let statusText;
|
||||||
|
|
||||||
|
if(response.status == 200) {
|
||||||
|
statusText = document.createTextNode(" Расписание успешно изменено.");
|
||||||
|
} else if(response.status == 400) {
|
||||||
|
statusText = document.createTextNode(" Ошибка при изменении расписания: неправильный формат данных.");
|
||||||
|
} else if(response.status == 401) {
|
||||||
|
statusText = document.createTextNode(" Неверный пароль.");
|
||||||
|
} else if(response.status == 500) {
|
||||||
|
statusText = document.createTextNode(" Ошибка при изменении расписания: внутренняя ошибка сервера. Попробуйте отправить изменения ещё раз прямо сейчас или чуть позже.");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendButton.after(statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayNetError(e) {
|
||||||
|
let sendButton = document.getElementById("send");
|
||||||
|
let statusText = document.createTextNode(" Сетевая ошибка при отправке изменений. Возможно, вам стоит проверить своё интернет-соединение или подождать, а затем отправить изменения снова.");
|
||||||
|
sendButton.after(statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendEdits() {
|
||||||
|
const schedule = document.getElementById("schedule");
|
||||||
|
let lessonsCount = 0;
|
||||||
|
let indices = [];
|
||||||
|
let names = [];
|
||||||
|
let namesLens = [];
|
||||||
|
let descs = [];
|
||||||
|
let descsLens = [];
|
||||||
|
let index = 0;
|
||||||
|
let textEncoder = new TextEncoder();
|
||||||
|
let password = textEncoder.encode(document.getElementById("password").value);
|
||||||
|
let bufferSize = 24 + password.length;
|
||||||
|
for(const row of schedule.children[0].children) {
|
||||||
|
if(row.children[1].children[0].checked) {
|
||||||
|
lessonsCount++;
|
||||||
|
indices.push(index);
|
||||||
|
let name = textEncoder.encode(row.children[2].children[0].value);
|
||||||
|
names.push(name);
|
||||||
|
namesLens.push(name.length);
|
||||||
|
let desc = textEncoder.encode(row.children[3].children[0].value);
|
||||||
|
descs.push(desc);
|
||||||
|
descsLens.push(desc.length);
|
||||||
|
bufferSize += 24 + name.length + desc.length;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = new Uint8Array(new ArrayBuffer(bufferSize));
|
||||||
|
|
||||||
|
numToUint8Array(password.length, data, 0);
|
||||||
|
for(let i = 0; i < password.length; i++) {
|
||||||
|
data[8 + i] = password[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentOff = 16 + password.length;
|
||||||
|
numToUint8Array(lessonsCount, data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
for(let i = 0; i < lessonsCount; i++) {
|
||||||
|
numToUint8Array(indices[i], data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
|
||||||
|
numToUint8Array(namesLens[i], data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
for(let j = 0; j < namesLens[i]; j++) {
|
||||||
|
data[currentOff + j] = names[i][j];
|
||||||
|
}
|
||||||
|
currentOff += namesLens[i];
|
||||||
|
|
||||||
|
numToUint8Array(descsLens[i], data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
for(let j = 0; j < descsLens[i]; j++) {
|
||||||
|
data[currentOff + j] = descs[i][j];
|
||||||
|
}
|
||||||
|
currentOff += descsLens[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = "?c=" + (new URLSearchParams(document.location.search)).get("c")
|
||||||
|
let day = document.getElementById("date").valueAsNumber;
|
||||||
|
if(day != NaN) {
|
||||||
|
params += "&d=" + (day / 86400000).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
params,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: data
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
displayStatus,
|
||||||
|
displayNetError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
123
index.html
Normal file
123
index.html
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2023 Nikita Osokin.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Добро пожаловать!</h1>
|
||||||
|
<p>Здравствуй, клиент!</p>
|
||||||
|
|
||||||
|
<p>Список классов:</p>
|
||||||
|
|
||||||
|
<table id="classes" border=1>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const classes = document.getElementById("classes");
|
||||||
|
|
||||||
|
function uint8ArrayToNum(arr, off) {
|
||||||
|
let result = 0;
|
||||||
|
for(var i = 0; i < 8; i++) {
|
||||||
|
result <<= 8;
|
||||||
|
result |= arr[off + i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayGetError(e) {
|
||||||
|
let p = document.createElement("p");
|
||||||
|
p.appendChild(document.createTextNode("Не удалось получить список классов от сервера."));
|
||||||
|
classes.before(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClasses() {
|
||||||
|
fetch(
|
||||||
|
"../getClassesList",
|
||||||
|
{
|
||||||
|
method: "GET"
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
getData,
|
||||||
|
displayGetError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(response) {
|
||||||
|
if(response.status != 200) {
|
||||||
|
displayGetError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.arrayBuffer().then(
|
||||||
|
displayData,
|
||||||
|
displayGetError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData(dataBuffer) {
|
||||||
|
let data = new Uint8Array(dataBuffer);
|
||||||
|
|
||||||
|
let classesCount = uint8ArrayToNum(data, 0);
|
||||||
|
let textDecoder = new TextDecoder();
|
||||||
|
let currentOff = 8;
|
||||||
|
for(let i = 0; i < classesCount; i++) {
|
||||||
|
let nRow = document.createElement("tr");
|
||||||
|
|
||||||
|
let len = uint8ArrayToNum(data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
let className = textDecoder.decode(data.slice(currentOff, currentOff + len));
|
||||||
|
let nCell1 = document.createElement("th");
|
||||||
|
nCell1.appendChild(document.createTextNode(className));
|
||||||
|
currentOff += len;
|
||||||
|
nRow.appendChild(nCell1);
|
||||||
|
|
||||||
|
|
||||||
|
let nCell2 = document.createElement("th");
|
||||||
|
let nA1 = document.createElement("a");
|
||||||
|
nA1.href = "../schedule?c=" + className;
|
||||||
|
nA1.appendChild(document.createTextNode("Расписание"));
|
||||||
|
nCell2.appendChild(nA1);
|
||||||
|
nRow.appendChild(nCell2);
|
||||||
|
|
||||||
|
let nCell3 = document.createElement("th");
|
||||||
|
let nA2 = document.createElement("a");
|
||||||
|
nA2.href = "../editpage?c=" + className;
|
||||||
|
nA2.appendChild(document.createTextNode("Изменить"));
|
||||||
|
nCell3.appendChild(nA2);
|
||||||
|
nRow.appendChild(nCell3);
|
||||||
|
|
||||||
|
classes.children[0].appendChild(nRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getClasses();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
411
main.go
Normal file
411
main.go
Normal file
|
@ -0,0 +1,411 @@
|
||||||
|
// citrusNikOs' journal -- минималистичный сервер для веб-журнала с расписанием.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2023 Nikita Osokin.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
// provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
// conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
// with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var schedFileRWMutex sync.RWMutex
|
||||||
|
var classesRWMutexes map[string]*sync.RWMutex
|
||||||
|
var classesNames []string
|
||||||
|
|
||||||
|
func isValidClass(class string) bool {
|
||||||
|
for i := 0; i < len(classesNames); i++ {
|
||||||
|
if classesNames[i] == class {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentDay() uint64 {
|
||||||
|
return uint64(time.Now().Unix()) / 86400
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidDay(day string) bool {
|
||||||
|
if len(day) == 0 || (day[0] == '0' && len(day) != 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(day); i++ {
|
||||||
|
if day[i] < '0' || day[i] > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func read8BytesBE(reader io.Reader, file *os.File) (uint64, error) {
|
||||||
|
var result uint64
|
||||||
|
var err error
|
||||||
|
var count int
|
||||||
|
buffer := make([]byte, 8)
|
||||||
|
|
||||||
|
count, err = reader.Read(buffer)
|
||||||
|
if count != 8 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
result <<= 8
|
||||||
|
result |= uint64(buffer[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = (*file).Write(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func read8BytesBENoCopy(reader io.Reader) (uint64, error) {
|
||||||
|
var result uint64
|
||||||
|
var err error
|
||||||
|
var count int
|
||||||
|
buffer := make([]byte, 8)
|
||||||
|
|
||||||
|
count, err = reader.Read(buffer)
|
||||||
|
if count != 8 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
result <<= 8
|
||||||
|
result |= uint64(buffer[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUint64BE(writer io.Writer, num uint64) error {
|
||||||
|
buffer := make([]byte, 8)
|
||||||
|
bufferI := 0
|
||||||
|
for i := 56; i >= 0; i -= 8 {
|
||||||
|
buffer[bufferI] = uint8((num >> i) & 0xFF)
|
||||||
|
bufferI++
|
||||||
|
}
|
||||||
|
_, err := writer.Write(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondWithFile(writer http.ResponseWriter, request *http.Request, filename string) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(" !!! Файл", filename, "не найден!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(" !!! Не удалось получить информацию о файле", filename + "!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer := make([]byte, stat.Size())
|
||||||
|
|
||||||
|
count, err := file.Read(buffer);
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(" !!! Не удалось прочитать содержимое файла", filename + "!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if int64(count) != stat.Size() {
|
||||||
|
fmt.Println(" *** Файл", filename, "был прочитан не полностью.")
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRootAnd404(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.URL.Path != "/" {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
respondWithFile(writer, request, "404.html")
|
||||||
|
} else {
|
||||||
|
respondWithFile(writer, request, "index.html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetClassesList(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
err := writeUint64BE(writer, uint64(len(classesNames)))
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(classesNames); i++ {
|
||||||
|
err = writeUint64BE(writer, uint64(len(classesNames[i])))
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = writer.Write([]uint8(classesNames[i]))
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleEditPage(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.Method == http.MethodPost {
|
||||||
|
queryValues := request.URL.Query()
|
||||||
|
|
||||||
|
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var day string
|
||||||
|
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
|
||||||
|
day = strconv.FormatUint(getCurrentDay(), 10)
|
||||||
|
} else {
|
||||||
|
day = queryValues["d"][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordLen, err := read8BytesBENoCopy(request.Body);
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordFile, err := os.Open("classes/" + queryValues["c"][0] + "/password")
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordFileInfo, err := passwordFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
passwordFile.Close()
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint64(passwordFileInfo.Size() - 1) != passwordLen {
|
||||||
|
passwordFile.Close()
|
||||||
|
writer.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userPassword := make([]byte, passwordLen)
|
||||||
|
passwordCount, err := request.Body.Read(userPassword)
|
||||||
|
if uint64(passwordCount) != passwordLen || err != nil {
|
||||||
|
passwordFile.Close()
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := make([]byte, passwordLen)
|
||||||
|
passwordCount, err = passwordFile.Read(password)
|
||||||
|
if uint64(passwordCount) != passwordLen || err != nil {
|
||||||
|
passwordFile.Close()
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < passwordLen; i++ {
|
||||||
|
if password[i] != userPassword[i] {
|
||||||
|
passwordFile.Close()
|
||||||
|
writer.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpSchedFile, err := os.CreateTemp("", "sched")
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutID, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||||
|
if err != nil || layoutID != 0 {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove(tmpSchedFile.Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lessonsCount, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||||
|
if err != nil || lessonsCount > 7 {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, 1)
|
||||||
|
lastNumber := uint64(0)
|
||||||
|
isFirst := true
|
||||||
|
for i := uint64(0); i < lessonsCount; i++ {
|
||||||
|
number, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !isFirst && number <= lastNumber {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isFirst = false
|
||||||
|
lastNumber = number
|
||||||
|
|
||||||
|
nameLen, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := uint64(0); j < nameLen; j++ {
|
||||||
|
beenRead, _ := request.Body.Read(buffer)
|
||||||
|
_, err := (*tmpSchedFile).Write(buffer)
|
||||||
|
|
||||||
|
if beenRead == 0 || err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
descrLen, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := uint64(0); j < descrLen; j++ {
|
||||||
|
beenRead, _ := request.Body.Read(buffer)
|
||||||
|
_, err := (*tmpSchedFile).Write(buffer)
|
||||||
|
|
||||||
|
if beenRead == 0 || err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beenRead, err := request.Body.Read(buffer)
|
||||||
|
if beenRead != 0 {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Remove((*tmpSchedFile).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
classesRWMutexes[queryValues["c"][0]].Lock()
|
||||||
|
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
|
||||||
|
|
||||||
|
os.Remove(schedFileName)
|
||||||
|
os.Rename((*tmpSchedFile).Name(), schedFileName)
|
||||||
|
|
||||||
|
classesRWMutexes[queryValues["c"][0]].Unlock()
|
||||||
|
} else {
|
||||||
|
respondWithFile(writer, request, "editpage.html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* TODO: в эту и похожие функции нужно добавть возможность отправить статус 500 */
|
||||||
|
func handleSchedule(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
respondWithFile(writer, request, "schedule.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: возможно стоит отправлять что-то кроме просто ошибки 500 */
|
||||||
|
func handleScheduleGet(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
queryValues := request.URL.Query()
|
||||||
|
|
||||||
|
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var day string
|
||||||
|
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
|
||||||
|
day = strconv.FormatUint(getCurrentDay(), 10)
|
||||||
|
} else {
|
||||||
|
day = queryValues["d"][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
classesRWMutexes[queryValues["c"][0]].RLock()
|
||||||
|
defer classesRWMutexes[queryValues["c"][0]].RUnlock()
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
var schedFile *os.File
|
||||||
|
var err error
|
||||||
|
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
|
||||||
|
for schedFile, err = os.Open(schedFileName); err != nil; schedFile, err = os.Open(schedFileName) {
|
||||||
|
if i == 1000 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(writer, schedFile)
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
schedFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
classesRWMutexes = make(map[string]*sync.RWMutex)
|
||||||
|
|
||||||
|
var classesDir *os.File
|
||||||
|
var classes []os.DirEntry
|
||||||
|
var err error
|
||||||
|
|
||||||
|
classesDir, err = os.Open("classes")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[!!!]Не удалось открыть директорию classes.\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
classes, err = classesDir.ReadDir(0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[!!!]Не удалось прочитать содержимое директории classes.\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
classesNames = make([]string, len(classes))
|
||||||
|
|
||||||
|
for i := 0; i < len(classes); i++ {
|
||||||
|
if !classes[i].IsDir() {
|
||||||
|
fmt.Printf("[!!!]Директория classes должна содержать только директории.\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
classesNames[i] = classes[i].Name()
|
||||||
|
classesRWMutexes[classes[i].Name()] = new(sync.RWMutex)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/", handleRootAnd404)
|
||||||
|
http.HandleFunc("/getClassesList", handleGetClassesList)
|
||||||
|
http.HandleFunc("/schedule", handleSchedule)
|
||||||
|
http.HandleFunc("/scheduleGet", handleScheduleGet)
|
||||||
|
http.HandleFunc("/editpage", handleEditPage)
|
||||||
|
http.ListenAndServe(":62314", nil)
|
||||||
|
}
|
||||||
|
|
126
schedule.html
Normal file
126
schedule.html
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<!--
|
||||||
|
Copyright (c) 2023 Nikita Osokin.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<input type=date id="date" />
|
||||||
|
<input type=button onclick="changeDay()" value="Получить расписание" />
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<table id="schedule" border=1>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const schedule = document.getElementById("schedule");
|
||||||
|
|
||||||
|
function changeDay() {
|
||||||
|
document.location.assign("?c=" + (new URLSearchParams(document.location.search)).get("c") + "&d=" + (document.getElementById("date").valueAsNumber / 86400000).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
function uint8ArrayToNum(arr, off) {
|
||||||
|
let result = 0;
|
||||||
|
for(var i = 0; i < 8; i++) {
|
||||||
|
result <<= 8;
|
||||||
|
result |= arr[off + i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayGetError(e) {
|
||||||
|
let p = document.createElement("p");
|
||||||
|
p.appendChild(document.createTextNode("Не удалось получить расписание от сервера."));
|
||||||
|
schedule.before(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSchedule() {
|
||||||
|
fetch(
|
||||||
|
"../scheduleGet" + document.location.search,
|
||||||
|
{
|
||||||
|
method: "GET"
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
getData,
|
||||||
|
displayGetError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(response) {
|
||||||
|
if(response.status != 200) {
|
||||||
|
displayGetError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.arrayBuffer().then(
|
||||||
|
displayData,
|
||||||
|
displayGetError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData(dataBuffer) {
|
||||||
|
let data = new Uint8Array(dataBuffer);
|
||||||
|
|
||||||
|
let lessonsCount = uint8ArrayToNum(data, 8);
|
||||||
|
let textDecoder = new TextDecoder();
|
||||||
|
let currentOff = 16;
|
||||||
|
for(let i = 0; i < lessonsCount; i++) {
|
||||||
|
let nRow = document.createElement("tr");
|
||||||
|
|
||||||
|
let nCell1 = document.createElement("th");
|
||||||
|
nCell1.appendChild(document.createTextNode(uint8ArrayToNum(data, currentOff) + 1));
|
||||||
|
currentOff += 8;
|
||||||
|
nRow.appendChild(nCell1);
|
||||||
|
|
||||||
|
let len = uint8ArrayToNum(data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
let nCell2 = document.createElement("th");
|
||||||
|
nCell2.appendChild(document.createTextNode(textDecoder.decode(data.slice(currentOff, currentOff + len))));
|
||||||
|
currentOff += len;
|
||||||
|
nRow.appendChild(nCell2);
|
||||||
|
|
||||||
|
len = uint8ArrayToNum(data, currentOff);
|
||||||
|
currentOff += 8;
|
||||||
|
let nCell3 = document.createElement("th");
|
||||||
|
nCell3.appendChild(document.createTextNode(textDecoder.decode(data.slice(currentOff, currentOff + len))));
|
||||||
|
currentOff += len;
|
||||||
|
nRow.appendChild(nCell3);
|
||||||
|
|
||||||
|
schedule.children[0].appendChild(nRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let day = (new URLSearchParams(document.location.search)).get("d");
|
||||||
|
if(day != null) {
|
||||||
|
document.getElementById("date").valueAsNumber = day * 86400000;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchedule();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue