group screen, server update && note img

This commit is contained in:
moxitech 2024-09-01 19:43:58 +07:00
parent cc19df62c4
commit 641b7fe7db
13 changed files with 575 additions and 250 deletions

6
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli
expo-env.d.ts
# @end expo-cli

View File

@ -17,12 +17,21 @@ export default function TabLayout() {
<Tabs.Screen
name="index"
options={{
title: 'Заметки',
title: 'Заметка',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'home' : 'home-outline'} color={color} />
),
}}
/>
<Tabs.Screen
name="groups"
options={{
title: 'Группы',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'grid' : 'grid-outline'} color={color} />
),
}}
/>
<Tabs.Screen
name="explore"
options={{

View File

@ -15,75 +15,7 @@ export default function TabTwoScreen() {
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Settings</ThemedText>
</ThemedView>
<ThemedText>This code can be help you.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens:{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
sets up the tab navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
different screen densities
</ThemedText>
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Custom fonts">
<ThemedText>
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
custom fonts such as this one.
</ThemedText>
</ThemedText>
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{' '}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
what the user's current color scheme is, and so you can adjust UI colors accordingly.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Animations">
<ThemedText>
This template includes an example of an animated component. The{' '}
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText> library
to create a waving hand animation.
</ThemedText>
{Platform.select({
ios: (
<ThemedText>
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
component provides a parallax effect for the header image.
</ThemedText>
),
})}
</Collapsible>
</ParallaxScrollView>
);
}

View File

@ -1,107 +0,0 @@
import { Image, StyleSheet, Platform, Button, ActivityIndicator } from 'react-native';
import ParallaxScrollView from '@/components/ParallaxScrollView';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { useEffect, useState } from 'react';
interface Message {
title: string;
type: string;
additionally: {
payload: string;
};
}
interface Data {
users: string[];
messages: Message[];
}
export default function GroupScreen() {
const [groupData, setGroupData] = useState<Data | null>(null);
useEffect(() => {
fetch("http://127.0.0.1:8099/grouphash:1/reloadDataFromServer")
.then(v => v.json())
.then((data: Data) => {
setGroupData(data);
})
.catch(e => {
console.log(e);
});
}, []);
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
headerImage={
<Image
source={require('@/assets/images/partial-react-logo.png')}
style={styles.reactLogo}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Пользователи</ThemedText>
{groupData?.users.map((user, index) => (
<ThemedText key={index}>{user}</ThemedText>
))}
</ThemedView>
<ThemedView >
{ groupData === null ?
<ActivityIndicator size="large" color="#00ff00" /> :
groupData?.messages.map((message, index) => (
<ThemedView key={index} style={styles.Message}>
<ThemedText>{message.title}</ThemedText>
{ message.type === "text" ?
<ThemedText>{message.additionally.payload}</ThemedText> :
<ThemedText>!!! Please Update Program !!!</ThemedText>
}
</ThemedView>
))
}
</ThemedView>
<ThemedView style={styles.buttonAddStyle}>
<Button
// onPress={onPressLearnMore}
title="+"
color="green"
accessibilityLabel="Add new block"
/>
</ThemedView>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
stepContainer: {
gap: 8,
marginBottom: 8,
},
reactLogo: {
height: 178,
width: 290,
bottom: 0,
left: 0,
position: 'absolute',
},
Message: {
backgroundColor: 'grey',
padding: 10,
marginBottom: 10,
borderRadius: 10,
},
buttonAddStyle: {
width: 50,
height: 60,
// flex: 1,
// justifyContent: 'end',
},
});

View File

@ -0,0 +1,232 @@
import { Image, StyleSheet, Button, ActivityIndicator, TextInput, Modal, Pressable, View } from 'react-native';
import ParallaxScrollView from '@/components/ParallaxScrollView';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { useEffect, useState } from 'react';
import NetInfo from '@react-native-community/netinfo';
interface Message {
title: string;
type: string;
additionally: {
payload: string;
};
}
interface GroupData {
groupName: string;
users: string[];
messages: Message[];
}
export default function GroupsScreen() {
const [groups, setGroups] = useState<GroupData[]>([]);
const [isSyncing, setIsSyncing] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [newGroupName, setNewGroupName] = useState<string>("");
const fetchGroups = async () => {
try {
setIsSyncing(true);
const response = await fetch("http://127.0.0.1:8099/getAllGroups");
const data: GroupData[] = await response.json();
setGroups(data);
} catch (error) {
console.log('Fetch error:', error);
} finally {
setIsSyncing(false);
}
};
const createGroup = async () => {
if (!newGroupName.trim()) return;
try {
const response = await fetch(`http://127.0.0.1:8099/create_group?group_name=` + newGroupName + `&username=` + "moxitech", {
method: "GET",
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
await fetchGroups(); // Обновляем список групп после создания новой
setIsModalVisible(false); // Закрываем модальное окно
setNewGroupName(""); // Сбрасываем имя новой группы
}
} catch (error) {
console.log('Create group error:', error);
}
};
useEffect(() => {
const syncWithServer = async () => {
await fetchGroups();
};
const unsubscribe = NetInfo.addEventListener(state => {
if (state.isConnected) {
syncWithServer();
}
});
// Первоначальная загрузка данных
syncWithServer();
return () => {
unsubscribe();
};
}, []);
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
headerImage={
<Image
source={require('@/assets/images/partial-react-logo.png')}
style={styles.reactLogo}
/>
}>
<ThemedView style={styles.buttonContainer}>
<Button
title="Создать группу"
color="green"
onPress={() => setIsModalVisible(true)}
/>
</ThemedView>
{/* {isSyncing ? (
<ActivityIndicator size="large" color="#00ff00" />
) : (
<Text>None</Text>
// groups.map( (group, groupIndex) => (
// <ThemedView key={groupIndex} style={styles.groupContainer}>
// <ThemedText type="title">{group.groupName}</ThemedText>
// <ThemedView style={styles.titleContainer}>
// <ThemedText type="subtitle">Пользователи</ThemedText>
// {group.users.map((user, userIndex) => (
// <ThemedText key={userIndex}>{user}</ThemedText>
// ))}
// </ThemedView>
// <ThemedView>
// {group.messages.map((message, messageIndex) => (
// <ThemedView key={messageIndex} style={styles.Message}>
// <ThemedText>{message.title}</ThemedText>
// {message.type === "text" ?
// <ThemedText>{message.additionally.payload}</ThemedText> :
// <ThemedText>!!! Please Update Program !!!</ThemedText>
// }
// </ThemedView>
// ))}
// </ThemedView>
// </ThemedView>
// ))
)
} */}
<Modal
animationType="slide"
transparent={true}
visible={isModalVisible}
onRequestClose={() => setIsModalVisible(false)}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<ThemedText type="title">Создать новую группу</ThemedText>
<TextInput
style={styles.input}
placeholder="Введите имя группы"
value={newGroupName}
onChangeText={setNewGroupName}
/>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={createGroup}
>
<ThemedText style={styles.textStyle}>Создать</ThemedText>
</Pressable>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={() => setIsModalVisible(false)}
>
<ThemedText style={styles.textStyle}>Отмена</ThemedText>
</Pressable>
</View>
</View>
</Modal>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
groupContainer: {
marginBottom: 20,
},
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
reactLogo: {
height: 178,
width: 290,
bottom: 0,
left: 0,
position: 'absolute',
},
Message: {
backgroundColor: 'grey',
padding: 10,
marginBottom: 10,
borderRadius: 10,
},
buttonContainer: {
margin: 10,
alignItems: 'center',
},
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
margin: 20,
backgroundColor: 'white',
borderRadius: 20,
padding: 35,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
button: {
borderRadius: 20,
padding: 10,
elevation: 2,
},
buttonClose: {
backgroundColor: '#2196F3',
marginTop: 10,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
width: 200,
},
textStyle: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
});

View File

@ -1,9 +1,9 @@
import { Image, StyleSheet, Platform, Button, ActivityIndicator } from 'react-native';
import { Image, StyleSheet, Button, ActivityIndicator, TextInput, Alert } from 'react-native';
import ParallaxScrollView from '@/components/ParallaxScrollView';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { useEffect, useState } from 'react';
import NetInfo from '@react-native-community/netinfo';
interface Message {
title: string;
@ -20,18 +20,83 @@ interface Data {
export default function HomeScreen() {
const [groupData, setGroupData] = useState<Data | null>(null);
const [newMessageTitle, setNewMessageTitle] = useState<string>('');
const [newMessagePayload, setNewMessagePayload] = useState<string>('');
const [isSyncing, setIsSyncing] = useState<boolean>(false);
const fetchData = async () => {
try {
const response = await fetch("http://127.0.0.1:8099/grouphash:1/reloadDataFromServer");
const data: Data = await response.json();
setGroupData(data);
} catch (error) {
console.log('Fetch error:', error);
}
};
const addNewMessage = async () => {
if (!newMessageTitle || !newMessagePayload) {
Alert.alert("Error", "Please fill in both the title and the content of the message.");
return;
}
const newMessage: Message = {
title: newMessageTitle,
type: "text",
additionally: {
payload: newMessagePayload,
}
};
// Мгновенное обновление локального состояния
setGroupData(prevData => {
if (prevData) {
return {
...prevData,
messages: [...prevData.messages, newMessage],
};
}
return null;
});
setNewMessageTitle('');
setNewMessagePayload('');
// Отправка новой заметки на сервер
try {
await fetch("http://127.0.0.1:8099/grouphash:1/addMessage", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newMessage),
});
} catch (error) {
console.log('Error syncing new message:', error);
}
};
useEffect(() => {
fetch("http://127.0.0.1:8099/grouphash:1/reloadDataFromServer")
.then(v => v.json())
.then((data: Data) => {
setGroupData(data);
})
.catch(e => {
console.log(e);
});
const syncWithServer = async () => {
setIsSyncing(true);
await fetchData();
setIsSyncing(false);
};
const unsubscribe = NetInfo.addEventListener(state => {
if (state.isConnected) {
syncWithServer();
}
});
// Первоначальная загрузка данных
syncWithServer();
return () => {
unsubscribe();
};
}, []);
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
@ -49,8 +114,8 @@ export default function HomeScreen() {
))}
</ThemedView>
<ThemedView >
{ groupData === null ?
<ThemedView>
{isSyncing ?
<ActivityIndicator size="large" color="#00ff00" /> :
groupData?.messages.map((message, index) => (
<ThemedView key={index} style={styles.Message}>
@ -59,14 +124,29 @@ export default function HomeScreen() {
<ThemedText>{message.additionally.payload}</ThemedText> :
<ThemedText>!!! Please Update Program !!!</ThemedText>
}
</ThemedView>
))
}
</ThemedView>
<ThemedView style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Title"
value={newMessageTitle}
onChangeText={setNewMessageTitle}
/>
<TextInput
style={styles.input}
placeholder="Content"
value={newMessagePayload}
onChangeText={setNewMessagePayload}
/>
</ThemedView>
<ThemedView style={styles.buttonAddStyle}>
<Button
// onPress={onPressLearnMore}
onPress={addNewMessage}
title="+"
color="green"
accessibilityLabel="Add new block"
@ -75,16 +155,13 @@ export default function HomeScreen() {
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
stepContainer: {
gap: 8,
marginBottom: 8,
},
reactLogo: {
height: 178,
width: 290,
@ -101,7 +178,15 @@ const styles = StyleSheet.create({
buttonAddStyle: {
width: 50,
height: 60,
// flex: 1,
// justifyContent: 'end',
},
inputContainer: {
padding: 10,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingHorizontal: 10,
},
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@react-native-community/netinfo": "^11.3.2",
"@react-navigation/native": "^6.0.2",
"expo": "~51.0.28",
"expo-constants": "~16.0.2",
@ -5869,6 +5870,14 @@
"node": ">=8"
}
},
"node_modules/@react-native-community/netinfo": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz",
"integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==",
"peerDependencies": {
"react-native": ">=0.59"
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.74.87",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz",

View File

@ -16,6 +16,7 @@
},
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@react-native-community/netinfo": "^11.3.2",
"@react-navigation/native": "^6.0.2",
"expo": "~51.0.28",
"expo-constants": "~16.0.2",

View File

@ -9,6 +9,8 @@ require (
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/fasthttp/websocket v1.5.3 // indirect
github.com/gofiber/websocket/v2 v2.2.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
@ -17,6 +19,7 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect

View File

@ -2,8 +2,12 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@ -23,6 +27,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=

View File

@ -1,12 +1,189 @@
package fiber
import (
"fmt"
"os"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/websocket/v2"
)
// Структуры для данных
type User struct {
Username string `json:"username"`
Secret []string `json:"secret_words"`
}
type Group struct {
Name string `json:"name"`
Users []string `json:"users"`
Messages []Message `json:"messages"`
}
type Message struct {
Title string `json:"title"`
Type string `json:"type"`
Additionally struct {
Timestamp int64 `json:"ts"`
Payload string `json:"payload"`
} `json:"additionally"`
}
var (
users = make(map[string]User) // карта пользователей по никнейму
groups = make(map[string]Group) // карта групп по имени
)
func getAllGroups(c *fiber.Ctx) error {
// groupList := []GroupData{}
// for _, group := range groups {
// groupList = append(groupList, group)
// }
return c.JSON(groups)
}
// Функция генерации уникального набора слов (можно заменить на более сложную логику)
func generateSecretWords() []string {
return []string{"word1", "word2", "word3", "word4", "word5", "word6", "word7", "word8"}
}
// Обработчик регистрации
func register(c *fiber.Ctx) error {
username := c.Query("username")
if username == "" {
return c.Status(400).JSON(fiber.Map{"error": "Username is required"})
}
if _, exists := users[username]; exists {
return c.Status(400).JSON(fiber.Map{"error": "Username already exists"})
}
secretWords := generateSecretWords()
users[username] = User{
Username: username,
Secret: secretWords,
}
return c.JSON(fiber.Map{
"username": username,
"secret_words": secretWords,
})
}
// Обработчик создания группы
func createGroup(c *fiber.Ctx) error {
groupName := c.Query("group_name")
username := c.Query("username")
if groupName == "" || username == "" {
return c.Status(400).JSON(fiber.Map{"error": "Group name and username are required"})
}
if _, exists := groups[groupName]; exists {
return c.Status(400).JSON(fiber.Map{"error": "Group already exists"})
}
if _, exists := users[username]; !exists {
return c.Status(400).JSON(fiber.Map{"error": "User does not exist"})
}
group := Group{
Name: groupName,
Users: []string{username},
Messages: []Message{},
}
groups[groupName] = group
return c.JSON(group)
}
// Обработчик добавления пользователя в группу
func addUserToGroup(c *fiber.Ctx) error {
groupName := c.Query("group_name")
username := c.Query("username")
if groupName == "" || username == "" {
return c.Status(400).JSON(fiber.Map{"error": "Group name and username are required"})
}
group, exists := groups[groupName]
if !exists {
return c.Status(400).JSON(fiber.Map{"error": "Group does not exist"})
}
if _, exists := users[username]; !exists {
return c.Status(400).JSON(fiber.Map{"error": "User does not exist"})
}
group.Users = append(group.Users, username)
groups[groupName] = group
return c.JSON(group)
}
// Обработчик подключения через WebSocket для синхронизации заметок
func syncGroup(c *fiber.Ctx) error {
groupName := c.Params("grouphash")
if websocket.IsWebSocketUpgrade(c) {
c.Locals("groupName", groupName)
return c.Next()
}
return c.Status(426).SendString("Upgrade required")
}
func handleWebSocket(c *websocket.Conn) {
groupName := c.Locals("groupName").(string)
for {
// Пример синхронизации: получение сообщения и его обработка
msgType, msg, err := c.ReadMessage()
if err != nil {
break
}
// Обновляем данные группы (на практике нужно добавить больше логики)
group := groups[groupName]
group.Messages = append(group.Messages, Message{
Title: string(msg),
Type: "text",
Additionally: struct {
Timestamp int64 `json:"ts"`
Payload string `json:"payload"`
}{
Timestamp: time.Now().Unix(),
Payload: string(msg),
},
})
groups[groupName] = group
// Отправка обновленных данных всем пользователям группы
for _, user := range group.Users {
fmt.Printf("Sending update to user: %s\n", user)
err = c.WriteMessage(msgType, []byte("New message in group: "+groupName))
if err != nil {
break
}
}
}
}
func addMessageToGroup(c *fiber.Ctx) error {
groupName := c.Params("grouphash")
group, exists := groups[groupName]
if !exists {
return c.Status(404).JSON(fiber.Map{"error": "Group not found"})
}
var newMessage Message
if err := c.BodyParser(&newMessage); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Invalid request payload"})
}
// Добавление метаданных
newMessage.Additionally.Timestamp = time.Now().Unix()
group.Messages = append(group.Messages, newMessage)
groups[groupName] = group
return c.JSON(group)
}
// Основная функция запуска сервера
func SpawnFiberCtx() error {
app := fiber.New()
app.Use(cors.New(cors.Config{
@ -14,60 +191,30 @@ func SpawnFiberCtx() error {
AllowMethods: "*",
AllowHeaders: "*",
}))
// app.Use()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendStatus(404)
})
// Login endpoint
app.Get("/login", func(c *fiber.Ctx) error {
return c.SendStatus(404)
})
// Register endpoint
app.Get("/register", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"Login": "Moxitech",
"Secret words": []string{
"Moxi",
"Tech",
"Cruiser",
"Well",
"Small",
"NotBad",
},
})
})
// Give websocket connection for sync groups list
app.Get("/connect", func(c *fiber.Ctx) error {
return c.SendStatus(404)
})
// Give websocket connection for sync concrete group
app.Get("/connect/grouphash::grouphash/", func(c *fiber.Ctx) error {
return c.SendStatus(404)
})
app.Get("/grouphash::grouphash/reloadDataFromServer", func(c *fiber.Ctx) error {
return c.SendString(`
{
"users": [
"Aksina",
"Moxitech"
],
"messages": [
{
"title": "Выполнить конфигурацию проекта",
"type": "text",
"additionally": {
"payload": "information message"
}
},
{
"title": "Написать очередной сервис",
"type": "text",
"additionally": {
"payload": "Сервис Quic-GO..."
}
}
]
}
`)
})
// Регистрация пользователя
app.Get("/register", register)
// Создание группы
app.Get("/create_group", createGroup)
// Добавление пользователя в группу
app.Get("/add_user_to_group", addUserToGroup)
// Обработчик добавления нового сообщения в группу
app.Post("/grouphash::grouphash/addMessage", addMessageToGroup)
// Возвращает все группы
app.Get("/getAllGroups", getAllGroups)
// Подключение к группе через WebSocket
app.Get("/connect/grouphash::grouphash", syncGroup)
// Обработчик для работы с WebSocket
app.Use("/ws", websocket.New(handleWebSocket))
return app.Listen("0.0.0.0:" + os.Getenv("API_PORT"))
}

View File

@ -2,6 +2,8 @@ package websocket
// Make conn pull && observable all data from group client to websocket
var ()
func Provider() {
}