+ const [form] = Form.useForm()
+ const onFinish = ({ code }) => {
+ ajax(`/api/engine/${params.gameId}/code`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ code })
+ })
+ .then((x) => {
+ if (x != null) {
+ setTask(x)
+ form.setFieldsValue({ code: '' })
+ }
+ }).catch(e => {
+ console.warn(e)
+ })
+ }
+
+ if (task && task.message === 'game_complete') {
+ return (
+ }
+ if (!task) {
+ return (
)
- }
+ }
- return (<>
+ return (<>
{task.title}
diff --git a/frontend/src/pages/Index.jsx b/frontend/src/pages/Index.jsx
index b55c674..d671bbc 100644
--- a/frontend/src/pages/Index.jsx
+++ b/frontend/src/pages/Index.jsx
@@ -5,9 +5,9 @@ import { UserProvider } from '../store/user'
const { Title, Paragraph } = Typography
const Index = () => {
- const { user } = UserProvider.useContainer()
- const navigate = useNavigate()
- return (<>
+ const { user } = UserProvider.useContainer()
+ const navigate = useNavigate()
+ return (<>
NQuest
Привет! Это платформа для ARG игр.
@@ -15,11 +15,11 @@ const Index = () => {
А если ты знаешь зачем пришёл, то добро пожаловать!
{!user
- ? (
+ ? (
)
- : ()}
+ : ()}
>)
}
diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx
index f53ae92..6a640f4 100644
--- a/frontend/src/pages/Login.jsx
+++ b/frontend/src/pages/Login.jsx
@@ -5,45 +5,45 @@ import { ajax } from '../utils/fetch'
import { Alert, Button, Form, Input } from 'antd'
const Login = () => {
- const { user, setUser } = UserProvider.useContainer()
- const { state } = useLocation()
- const [error, setError] = useState(null)
- const navigate = useNavigate()
- const [form] = Form.useForm()
+ const { user, setUser } = UserProvider.useContainer()
+ const { state } = useLocation()
+ const [error, setError] = useState(null)
+ const navigate = useNavigate()
+ const [form] = Form.useForm()
- useEffect(() => {
- if (user) {
- navigate(state && state.from ? state.from : '/')
+ useEffect(() => {
+ if (user) {
+ navigate(state && state.from ? state.from : '/')
+ }
+ }, [user])
+
+ const onFinish = (values) => {
+ ajax('/api/user/login', {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(values)
+ })
+ .then(setUser)
+ .catch(({ message }) => setError('Проверьте e-mail и пароль'))
}
- }, [user])
- const onFinish = (values) => {
- ajax('/api/user/login', {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(values)
- })
- .then(setUser)
- .catch(({ message }) => setError('Проверьте e-mail и пароль'))
- }
-
- return (<>
+ return (<>
Вход
{error ? : null}
diff --git a/frontend/src/pages/User.jsx b/frontend/src/pages/User.jsx
index f7852f9..a82dbf4 100644
--- a/frontend/src/pages/User.jsx
+++ b/frontend/src/pages/User.jsx
@@ -5,29 +5,29 @@ import Markdown from 'react-markdown'
const { Paragraph } = Typography
const User = () => {
- const { user } = UserProvider.useContainer()
- if (user == null) {
- return (Загрузка...)
- }
+ const { user } = UserProvider.useContainer()
+ if (user == null) {
+ return (Загрузка...)
+ }
- return (<>
+ return (<>
{user.username}
- Уровень: {user.level} ур
- Очков опыта: {user.experience} ОО
- Следующий уровень: {user.expToNextLevel} ОО
-
- {user.games.map(item => Уровень: {user.level} ур
+ Очков опыта: {user.experience} ОО
+ Следующий уровень: {user.expToNextLevel} ОО
+
+ {user.games.map(item => {item.description}}
- >
+ >
- )}
+ )}
>)
}
diff --git a/frontend/src/pages/admin/Quest.jsx b/frontend/src/pages/admin/Quest.jsx
index 4861821..aecadeb 100644
--- a/frontend/src/pages/admin/Quest.jsx
+++ b/frontend/src/pages/admin/Quest.jsx
@@ -9,121 +9,121 @@ import Markdown from 'react-markdown'
const { Title } = Typography
const Quest = () => {
- let { quest, files } = useLoaderData()
- const [error, setError] = useState()
- if (!quest) {
- quest = {
- type: 'city',
- points: 10,
- tasks: [],
- id: uuidv4(),
- visible: false,
- title: '',
- description: ''
+ let { quest, files } = useLoaderData()
+ const [error, setError] = useState()
+ if (!quest) {
+ quest = {
+ type: 'city',
+ points: 10,
+ tasks: [],
+ id: uuidv4(),
+ visible: false,
+ title: '',
+ description: ''
+ }
}
- }
- const [fields, setFields] = useState(quest)
- const [preview, setPreview] = useState(false)
- const navigate = useNavigate()
- const normFile = (e) => {
- if (Array.isArray(e)) {
- return e
+ const [fields, setFields] = useState(quest)
+ const [preview, setPreview] = useState(false)
+ const navigate = useNavigate()
+ const normFile = (e) => {
+ if (Array.isArray(e)) {
+ return e
+ }
+ if (e.file.response) {
+ return e.file.response.uuid
+ }
+
+ return e
}
- if (e.file.response) {
- return e.file.response.uuid
+ const formItemLayout = {
+ labelCol: { span: 6 },
+ wrapperCol: { span: 14 }
+ }
+ const buttonLayout = {
+ offset: 6,
+ span: 14
}
- return e
- }
- const formItemLayout = {
- labelCol: { span: 6 },
- wrapperCol: { span: 14 }
- }
- const buttonLayout = {
- offset: 6,
- span: 14
- }
+ const onFinish = (values) => {
+ ajax('/api/admin/games', {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(values)
+ })
+ .then(g => navigate(`/admin/quests/${g.id}/`))
+ .catch(({ message }) => setError('Ошибка создания'))
+ }
- const onFinish = (values) => {
- ajax('/api/admin/games', {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(values)
- })
- .then(g => navigate(`/admin/quests/${g.id}/`))
- .catch(({ message }) => setError('Ошибка создания'))
- }
-
- return (
+ return (
<>
{quest.title ? (quest.title) : ('Новый квест')}
{error ? : null}
-
-
-
-
- Сохранить квест
-
-
- setPreview(true)}>
- Предпросмотр
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {quest.icon ? : null}
-
- }>Загрузка
-
-
-
-
- Полевой
- Виртуальный
-
-
-
-
-
-
- {(tasks, { add, remove }) => (
- <>
- {tasks.map(renderTaskForm(remove))}
-
- add()} block>
- Добавить уровень
+
+
+
+
+ Сохранить квест
-
- >
- )}
-
-
+
+ setPreview(true)}>
+ Предпросмотр
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {quest.icon ? : null}
+
+ }>Загрузка
+
+
+
+
+ Полевой
+ Виртуальный
+
+
+
+
+
+
+ {(tasks, { add, remove }) => (
+ <>
+ {tasks.map(renderTaskForm(remove))}
+
+ add()} block>
+ Добавить уровень
+
+
+ >
+ )}
+
+
Файлы
@@ -132,41 +132,40 @@ const Quest = () => {
action={`/api/admin/file/${quest.id}/upload`}
listType='picture'
maxCount={10}
- itemRender={renderFile}
+ itemRender={(e, file) => renderFile(e, file, quest)}
>
}>Загрузка
Ранее загруженные файлы:
-
+ renderFileItem(x, quest)} />
-
-
setPreview(false)}>Закрыть}
- width={'80%'}
- centered
- >
- (
-
-
- {task.text}
-
+
+ setPreview(false)}>Закрыть}
+ width={'80%'}
+ centered
+ >
+ (
+
+
+ {task.text}
+
Коды:
-
+
{task.codes.map(c => - {c.code}
)}
-
- >
- }
- />
-
- )} />
-
- >
- )
+
+ >
+ }
+ />
+
+ )} />
+
+ >)
}
// eslint-disable-next-line react/display-name
@@ -184,11 +183,11 @@ const renderTaskForm = remove => task => (
cancelText='Нет'
>
- Удалить уровень
+ Удалить уровень
]}
- >
+ >
@@ -203,9 +202,9 @@ const renderTaskForm = remove => task => (
<>
{codes.map(renderCodeForm(codesOpts.remove))}
- codesOpts.add()} block>
- Добавить код
-
+ codesOpts.add()} block>
+ Добавить код
+
>
)}
@@ -219,19 +218,19 @@ const renderCodeForm = remove => code => (
key={code.key}
style={{ marginBottom: 8 }}
actions={[
- //
remove(code.name)}
- // okText='Да'
- // cancelText='Нет'
- // >
+ // remove(code.name)}
+ // okText='Да'
+ // cancelText='Нет'
+ // >
remove(code.name)}>
- Удалить код
+ Удалить код
- //
+ //
]}
- >
+ >
@@ -244,21 +243,24 @@ const renderCodeForm = remove => code => (
)
-const renderFile = (e, file) => (
-
- {e}
- {file && file.response && file.response.uuid
- ? <>Код для вставки:
![](/api/file/{file.response.uuid})
>
- : null}
-
-)
+const renderFile = (e, file, quest) => {
+ console.log(file)
+ return (
+
+ {e}
+ {file && file.response && file.response.uuid
+ ? <>Код для вставки:
![](/file/{quest.id}/{file.originFileObj.name})
>
+ : null}
+
+ )
+}
-const renderFileItem = (file) => (
+const renderFileItem = (file, quest) => (
}
+ avatar={}
title={file.originalName}
- description={<>Код для вставки: ![](/api/file/{file.id})
>}
+ description={<>Код для вставки: ![](/file/{quest.id}/{file.originalName})
>}
/>
diff --git a/frontend/src/pages/admin/Quests.jsx b/frontend/src/pages/admin/Quests.jsx
index 0c310a3..8084a13 100644
--- a/frontend/src/pages/admin/Quests.jsx
+++ b/frontend/src/pages/admin/Quests.jsx
@@ -4,9 +4,9 @@ import { Link, useLoaderData } from 'react-router-dom'
const { Title } = Typography
const Quests = () => {
- const quests = useLoaderData()
+ const quests = useLoaderData()
- return (
+ return (
<>
Управление своими квестами
Создать новый квест
@@ -14,29 +14,29 @@ const Quests = () => {
dataSource={quests}
rowKey={'id'}
columns={[
- {
- title: 'Опубликован?',
- dataIndex: 'visible',
- key: 'visible',
- render: visible => visible ? 'Да' : 'Нет'
- },
- {
- title: 'Название',
- dataIndex: 'title',
- key: 'title',
- render: (title, q) =>
{title}
- },
- {
- title: 'Тип',
- dataIndex: 'type',
- key: 'type',
- render: type => type === 'virtual' ? 'Виртуальный' : 'Полевой'
- }
+ {
+ title: 'Опубликован?',
+ dataIndex: 'visible',
+ key: 'visible',
+ render: visible => visible ? 'Да' : 'Нет'
+ },
+ {
+ title: 'Название',
+ dataIndex: 'title',
+ key: 'title',
+ render: (title, q) =>
{title}
+ },
+ {
+ title: 'Тип',
+ dataIndex: 'type',
+ key: 'type',
+ render: type => type === 'virtual' ? 'Виртуальный' : 'Полевой'
+ }
]}
/>
>
- )
+ )
}
export default Quests
diff --git a/frontend/src/store/provider.js b/frontend/src/store/provider.js
index d957bb9..09d707c 100644
--- a/frontend/src/store/provider.js
+++ b/frontend/src/store/provider.js
@@ -1,5 +1,5 @@
import { UserProvider } from './user'
export const store = [
- UserProvider.Provider
+ UserProvider.Provider
]
diff --git a/frontend/src/store/user.js b/frontend/src/store/user.js
index 2b32467..8868967 100644
--- a/frontend/src/store/user.js
+++ b/frontend/src/store/user.js
@@ -2,8 +2,8 @@ import { useState } from 'react'
import { createContainer } from 'unstated-next'
const useUser = () => {
- const [user, setUser] = useState(null)
- return { user, setUser }
+ const [user, setUser] = useState(null)
+ return { user, setUser }
}
export const UserProvider = createContainer(useUser)
diff --git a/frontend/src/utils/fetch.js b/frontend/src/utils/fetch.js
index 52fad33..910aa26 100644
--- a/frontend/src/utils/fetch.js
+++ b/frontend/src/utils/fetch.js
@@ -1,10 +1,10 @@
export const ajax = async (path, params) => {
- return fetch(path, params)
- .then(r => {
- if (r.status < 200 || r.status >= 300) {
- throw Error(r.statusText)
- }
- return r
- })
- .then(r => r.json())
+ return fetch(path, params)
+ .then(r => {
+ if (r.status < 200 || r.status >= 300) {
+ throw Error(r.statusText)
+ }
+ return r
+ })
+ .then(r => r.json())
}
diff --git a/frontend/src/utils/roles.js b/frontend/src/utils/roles.js
index 9a34442..db711c5 100644
--- a/frontend/src/utils/roles.js
+++ b/frontend/src/utils/roles.js
@@ -1,24 +1,24 @@
import { UserProvider } from '../store/user'
const roleHierarchy = {
- user: {
- user: true
- },
- creator: {
- user: true,
- creator: true
- },
- admin: {
- user: true,
- creator: true,
- admin: true
- }
+ user: {
+ user: true
+ },
+ creator: {
+ user: true,
+ creator: true
+ },
+ admin: {
+ user: true,
+ creator: true,
+ admin: true
+ }
}
export const useRole = () => {
- const { user } = UserProvider.useContainer()
+ const { user } = UserProvider.useContainer()
- return {
- hasRole: (role) => user && user.role && !!roleHierarchy[user.role][role]
- }
+ return {
+ hasRole: (role) => user && user.role && !!roleHierarchy[user.role][role]
+ }
}
diff --git a/frontend/src/utils/uuid.js b/frontend/src/utils/uuid.js
index d876381..f3f0120 100644
--- a/frontend/src/utils/uuid.js
+++ b/frontend/src/utils/uuid.js
@@ -1,5 +1,5 @@
export function uuidv4 () {
- return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
- (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
- )
+ return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
+ (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
+ )
};
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index c1d78c6..cf71160 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -3,88 +3,95 @@ import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
const manifest = {
- registerType: 'autoUpdate',
- includeAssets: ['assets/icon.png', 'assets/logo.png'],
- workbox: {
- cleanupOutdatedCaches: true
- },
- manifest: {
- name: 'NQuest',
- short_name: 'NQuest',
- description: 'NQuest - платформа для ARG игр.',
- icons: [
- {
- src: 'assets/icons/icon-72x72.png',
- sizes: '72x72',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-96x96.png',
- sizes: '96x96',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-128x128.png',
- sizes: '128x128',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-144x144.png',
- sizes: '144x144',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-152x152.png',
- sizes: '152x152',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-192x192.png',
- sizes: '192x192',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-384x384.png',
- sizes: '384x384',
- type: 'image/png',
- purpose: 'maskable any'
- },
- {
- src: 'assets/icons/icon-512x512.png',
- sizes: '512x512',
- type: 'image/png',
- purpose: 'maskable any'
- }
- ],
- theme_color: '#59FBEA',
- background_color: '#171e26',
- display: 'standalone',
- scope: '/',
- start_url: '/quests'
- }
+ registerType: 'autoUpdate',
+ includeAssets: ['assets/icon.png', 'assets/logo.png'],
+ workbox: {
+ cleanupOutdatedCaches: true
+ },
+ base: 'assets',
+ manifest: {
+ name: 'NQuest',
+ short_name: 'NQuest',
+ description: 'NQuest - платформа для ARG игр.',
+ icons: [
+ {
+ src: 'assets/icons/icon-72x72.png',
+ sizes: '72x72',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-96x96.png',
+ sizes: '96x96',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-128x128.png',
+ sizes: '128x128',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-144x144.png',
+ sizes: '144x144',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-152x152.png',
+ sizes: '152x152',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-192x192.png',
+ sizes: '192x192',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-384x384.png',
+ sizes: '384x384',
+ type: 'image/png',
+ purpose: 'maskable any'
+ },
+ {
+ src: 'assets/icons/icon-512x512.png',
+ sizes: '512x512',
+ type: 'image/png',
+ purpose: 'maskable any'
+ }
+ ],
+ theme_color: '#59FBEA',
+ background_color: '#171e26',
+ display: 'standalone',
+ scope: '/',
+ start_url: '/quests'
+ }
}
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react(), splitVendorChunkPlugin(), VitePWA(manifest)],
- server: {
- proxy: {
- '/api': {
- target: 'http://localhost:8000',
- changeOrigin: true,
- secure: false,
- ws: false
- }
- }
- },
- build: {
+ plugins: [react(), splitVendorChunkPlugin(), VitePWA(manifest)],
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8000',
+ changeOrigin: true,
+ secure: false,
+ ws: false
+ },
+ '/file': {
+ target: 'http://localhost:8000',
+ changeOrigin: true,
+ secure: false,
+ ws: false
+ }
+ }
+ },
+ build: {
// generate .vite/manifest.json in outDir
- manifest: true
- }
+ manifest: true
+ }
})
diff --git a/main.go b/main.go
index 40c246d..36954b2 100644
--- a/main.go
+++ b/main.go
@@ -127,12 +127,12 @@ func main() {
api.RegisterHandlersWithBaseURL(codegen, handler, "/api")
- e.FileFS("/", "index.html", distIndexHtml)
- e.FileFS("/*", "index.html", distIndexHtml)
- e.StaticFS("/", distDirFS)
-
// --[ System ]--
e.GET("/metrics", echoprometheus.NewHandler())
+
+ e.StaticFS("/file", afero.NewIOFS(storage))
+ e.StaticFS("/*", distDirFS)
+
e.Logger.Debugf("backend version %s", Version)
e.Logger.Fatal(e.Start(cfg.Listen))
}
diff --git a/pkg/controller/admin.go b/pkg/controller/admin.go
index 1167a72..7a03661 100644
--- a/pkg/controller/admin.go
+++ b/pkg/controller/admin.go
@@ -45,7 +45,7 @@ func (a *Admin) AdminEditGame(ctx echo.Context) error {
})
}
tasks = append(tasks, api.TaskEdit{
- Id: &t.ID,
+ Id: t.ID,
Codes: codes,
Text: t.Text,
Title: t.Title,
@@ -98,7 +98,7 @@ func (a *Admin) AdminGetGame(ctx echo.Context, uid uuid.UUID) error {
})
}
tasks = append(tasks, api.TaskEdit{
- Id: &t.ID,
+ Id: t.ID,
Codes: codes,
Text: t.Text,
Title: t.Title,
@@ -163,12 +163,8 @@ func (*Admin) mapCreateGameRequest(req *api.GameEdit, user *models.User) *models
IconID: req.Icon,
}
for order, te := range req.Tasks {
- id := uuid.New()
- if te.Id != nil {
- id = *te.Id
- }
task := &models.Task{
- ID: id,
+ ID: te.Id,
Title: te.Title,
Text: te.Text,
Codes: make([]*models.Code, 0, len(te.Codes)),
diff --git a/pkg/controller/file.go b/pkg/controller/file.go
index 531ae12..cb77e2c 100644
--- a/pkg/controller/file.go
+++ b/pkg/controller/file.go
@@ -40,16 +40,6 @@ func (u *File) AdminUploadFile(c echo.Context, quest uuid.UUID) error {
})
}
-// (GET /file/{uid})
-func (u *File) GetFile(c echo.Context, uid uuid.UUID) error {
- f, rdr, err := u.FileService.GetFile(c.Request().Context(), uid)
- if err != nil {
- return err
- }
-
- return c.Stream(200, f.ContentType, rdr)
-}
-
func (u *File) AdminListFiles(c echo.Context, quest uuid.UUID) error {
fl, err := u.FileService.GetFilesByQuest(c.Request().Context(), quest)
if err != nil {
diff --git a/pkg/models/task.go b/pkg/models/task.go
index 448c592..5d6e97d 100644
--- a/pkg/models/task.go
+++ b/pkg/models/task.go
@@ -10,6 +10,7 @@ type Task struct {
Title string
Text string
MaxTime int
+ Game *Game
GameID uuid.UUID
Codes []*Code `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
TaskOrder uint
diff --git a/pkg/service/file.go b/pkg/service/file.go
index ef976ca..b851f4d 100644
--- a/pkg/service/file.go
+++ b/pkg/service/file.go
@@ -3,7 +3,6 @@ package service
import (
"context"
"fmt"
- "io"
"mime/multipart"
"github.com/google/uuid"
@@ -53,21 +52,6 @@ func (u *File) Upload(
return file.ID, u.DB.WithContext(ctx).Create(file).Error
}
-func (u *File) GetFile(ctx context.Context, uid uuid.UUID) (*models.File, io.ReadCloser, error) {
- f := new(models.File)
- if err := u.DB.WithContext(ctx).First(f, uid).Error; err != nil {
- return nil, nil, err
- }
-
- filePath := fmt.Sprintf("%s/%s", f.QuestID.String(), f.Filename)
- file, err := u.store.Open(filePath)
- if err != nil {
- return nil, nil, err
- }
-
- return f, file, nil
-}
-
func (u *File) GetFilesByQuest(ctx context.Context, quest uuid.UUID) ([]*models.File, error) {
list := make([]*models.File, 0)
diff --git a/pkg/service/game.go b/pkg/service/game.go
index 6dda806..52b3441 100644
--- a/pkg/service/game.go
+++ b/pkg/service/game.go
@@ -68,7 +68,14 @@ func (gs *Game) ListByAuthor(ctx context.Context, author *models.User) ([]*model
}
func (gs *Game) UpsertGame(ctx context.Context, game *models.Game) (*models.Game, error) {
- return game, gs.DB.Debug().
+
+ ids := []uuid.UUID{}
+ for _, t := range game.Tasks {
+ ids = append(ids, t.ID)
+ }
+ gs.DB.Delete([]models.Task{}, `game_id = ? and id not in (?)`, game.ID, ids)
+
+ err := gs.DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
@@ -76,6 +83,11 @@ func (gs *Game) UpsertGame(ctx context.Context, game *models.Game) (*models.Game
"title", "description",
}),
}).
- Create(&game).
+ Create(game).
Error
+ if err != nil {
+ return nil, err
+ }
+
+ return game, gs.DB.Save(game).Error
}