chore: Remove Server Tasks (#3592)

This commit is contained in:
Michael Genson 2024-05-20 09:51:37 -05:00 committed by GitHub
parent 78d2a3b8aa
commit 61becdbec7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 12 additions and 341 deletions

File diff suppressed because one or more lines are too long

View File

@ -85,12 +85,6 @@ export default defineComponent({
title: i18n.tc("sidebar.maintenance"),
restricted: true,
},
{
icon: $globals.icons.check,
to: "/admin/background-tasks",
title: i18n.tc("sidebar.background-tasks"),
restricted: true,
},
{
icon: $globals.icons.slotMachine,
to: "/admin/parser",

View File

@ -1,19 +0,0 @@
import { BaseAPI } from "../base/base-clients";
import { ServerTask } from "~/lib/api/types/server";
import { PaginationData } from "~/lib/api/types/non-generated";
const prefix = "/api";
const routes = {
base: `${prefix}/admin/server-tasks`,
};
export class AdminTaskAPI extends BaseAPI {
async testTask() {
return await this.requests.post<ServerTask>(`${routes.base}`, {});
}
async getAll() {
return await this.requests.get<PaginationData<ServerTask>>(routes.base);
}
}

View File

@ -1,5 +1,4 @@
import { AdminAboutAPI } from "./admin/admin-about";
import { AdminTaskAPI } from "./admin/admin-tasks";
import { AdminUsersApi } from "./admin/admin-users";
import { AdminGroupsApi } from "./admin/admin-groups";
import { AdminBackupsApi } from "./admin/admin-backups";
@ -9,7 +8,6 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class AdminAPI {
public about: AdminAboutAPI;
public serverTasks: AdminTaskAPI;
public users: AdminUsersApi;
public groups: AdminGroupsApi;
public backups: AdminBackupsApi;
@ -18,7 +16,6 @@ export class AdminAPI {
constructor(requests: ApiRequestInstance) {
this.about = new AdminAboutAPI(requests);
this.serverTasks = new AdminTaskAPI(requests);
this.users = new AdminUsersApi(requests);
this.groups = new AdminGroupsApi(requests);
this.backups = new AdminBackupsApi(requests);

View File

@ -15,7 +15,6 @@ import { RegisterAPI } from "./user/user-registration";
import { MealPlanAPI } from "./user/group-mealplan";
import { EmailAPI } from "./user/email";
import { BulkActionsAPI } from "./user/recipe-bulk-actions";
import { GroupServerTaskAPI } from "./user/group-tasks";
import { ToolsApi } from "./user/organizer-tools";
import { GroupMigrationApi } from "./user/group-migrations";
import { GroupReportsApi } from "./user/group-reports";
@ -46,7 +45,6 @@ export class UserApiClient {
public bulk: BulkActionsAPI;
public groupMigration: GroupMigrationApi;
public groupReports: GroupReportsApi;
public grouperServerTasks: GroupServerTaskAPI;
public tools: ToolsApi;
public shopping: ShoppingApi;
public multiPurposeLabels: MultiPurposeLabelsApi;
@ -72,7 +70,6 @@ export class UserApiClient {
this.register = new RegisterAPI(requests);
this.mealplans = new MealPlanAPI(requests);
this.mealplanRules = new MealPlanRulesApi(requests);
this.grouperServerTasks = new GroupServerTaskAPI(requests);
// Group
this.groupMigration = new GroupMigrationApi(requests);

View File

@ -1,25 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type ServerTaskNames = "Background Task" | "Database Backup" | "Bulk Recipe Import";
export type ServerTaskStatus = "running" | "finished" | "failed";
export interface ServerTask {
groupId: string;
name?: ServerTaskNames & string;
createdAt?: string;
status?: ServerTaskStatus & string;
log?: string;
id: number;
}
export interface ServerTaskCreate {
groupId: string;
name?: ServerTaskNames & string;
createdAt?: string;
status?: ServerTaskStatus & string;
log?: string;
}

View File

@ -1,13 +0,0 @@
import { BaseAPI } from "../base/base-clients";
import { ServerTask } from "~/lib/api/types/server";
const prefix = "/api";
const routes = {
base: `${prefix}/groups/server-tasks`,
};
export class GroupServerTaskAPI extends BaseAPI {
async getAll() {
return await this.requests.get<ServerTask[]>(routes.base);
}
}

View File

@ -1,87 +0,0 @@
<template>
<v-container class="narrow-container">
<BasePageTitle divider>
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-tasks.svg')"></v-img>
</template>
<template #title> {{ $t('admin.background-tasks') }} </template>
{{ $t('admin.background-tasks-description') }}
</BasePageTitle>
<v-card-actions>
<BaseButton color="info" :loading="loading" @click="refreshTasks">
<template #icon> {{ $globals.icons.refresh }} </template>
{{ $t('general.refresh') }}
</BaseButton>
<BaseButton color="info" @click="testTask">
<template #icon> {{ $globals.icons.testTube }} </template>
{{ $t('general.test') }}
</BaseButton>
</v-card-actions>
<v-expansion-panels class="mt-2">
<v-expansion-panel v-for="(task, i) in tasks" :key="i">
<v-expansion-panel-header>
<span>
<v-progress-circular v-if="task.status === 'running'" indeterminate color="info"></v-progress-circular>
<v-icon v-else-if="task.status === 'finished'" large color="success"> {{ $globals.icons.check }}</v-icon>
<v-icon v-else-if="task.status === 'failed'" large color="error"> {{ $globals.icons.close }}</v-icon>
<v-icon v-else-if="task.status === 'pending'" large color="gray"> {{ $globals.icons.pending }}</v-icon>
<span class="ml-2">
{{ task.name }}
</span>
</span>
{{ $d(Date.parse(task.createdAt), "short") }}
</v-expansion-panel-header>
<v-expansion-panel-content style="white-space: pre-line">
{{ task.log === "" ? $t('admin.no-logs-found') : task.log }}
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-container>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import { ServerTask } from "~/lib/api/types/server";
import { useAdminApi } from "~/composables/api";
export default defineComponent({
layout: "admin",
setup() {
const api = useAdminApi();
const tasks = ref<ServerTask[]>([]);
const loading = ref(false);
async function refreshTasks() {
loading.value = true;
const { data } = await api.serverTasks.getAll();
if (data) {
tasks.value = data.items;
}
loading.value = false;
}
async function testTask() {
await api.serverTasks.testTask();
refreshTasks();
}
onMounted(async () => {
await refreshTasks();
});
return {
loading,
refreshTasks,
testTask,
tasks,
};
},
head() {
return {
title: this.$t("admin.tasks"),
};
},
});
</script>

View File

@ -15,13 +15,19 @@ from .._model_utils import GUID, auto_init
from ..group.invite_tokens import GroupInviteToken
from ..group.webhooks import GroupWebhooksModel
from ..recipe.category import Category, group_to_categories
from ..server.task import ServerTaskModel
from .cookbook import CookBook
from .mealplan import GroupMealPlan
from .preferences import GroupPreferencesModel
if TYPE_CHECKING:
from ..recipe import IngredientFoodModel, IngredientUnitModel, RecipeModel, Tag, Tool
from ..recipe import (
IngredientFoodModel,
IngredientUnitModel,
RecipeModel,
Tag,
Tool,
)
from ..server.task import ServerTaskModel
from ..users import User
from .events import GroupEventNotifierModel
from .exports import GroupDataExportsModel
@ -67,7 +73,7 @@ class Group(SqlAlchemyBase, BaseMixins):
webhooks: Mapped[list[GroupWebhooksModel]] = orm.relationship(GroupWebhooksModel, **common_args)
recipe_actions: Mapped[list["GroupRecipeAction"]] = orm.relationship("GroupRecipeAction", **common_args)
cookbooks: Mapped[list[CookBook]] = orm.relationship(CookBook, **common_args)
server_tasks: Mapped[list[ServerTaskModel]] = orm.relationship(ServerTaskModel, **common_args)
server_tasks: Mapped[list["ServerTaskModel"]] = orm.relationship("ServerTaskModel", **common_args)
data_exports: Mapped[list["GroupDataExportsModel"]] = orm.relationship("GroupDataExportsModel", **common_args)
shopping_lists: Mapped[list["ShoppingList"]] = orm.relationship("ShoppingList", **common_args)
group_reports: Mapped[list["ReportModel"]] = orm.relationship("ReportModel", **common_args)

View File

@ -14,6 +14,8 @@ if TYPE_CHECKING:
class ServerTaskModel(SqlAlchemyBase, BaseMixins):
# Server Tasks are deprecated, but the table still exists in the database
__tablename__ = "server_tasks"
name: Mapped[str] = mapped_column(String, nullable=False)
completed_date: Mapped[datetime] = mapped_column(DateTime, nullable=True)

View File

@ -29,7 +29,6 @@ from mealie.db.models.recipe.recipe_timeline import RecipeTimelineEvent
from mealie.db.models.recipe.shared import RecipeShareTokenModel
from mealie.db.models.recipe.tag import Tag
from mealie.db.models.recipe.tool import Tool
from mealie.db.models.server.task import ServerTaskModel
from mealie.db.models.users import LongLiveToken, User
from mealie.db.models.users.password_reset import PasswordResetModel
from mealie.db.models.users.user_to_recipe import UserToRecipe
@ -59,7 +58,6 @@ from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUni
from mealie.schema.recipe.recipe_share_token import RecipeShareToken
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventOut
from mealie.schema.reports.reports import ReportEntryOut, ReportOut
from mealie.schema.server import ServerTask
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser
from mealie.schema.user.user import UserRatingOut
from mealie.schema.user.user_passwords import PrivatePasswordResetToken
@ -162,10 +160,6 @@ class AllRepositories:
# ================================================================
# Group
@cached_property
def server_tasks(self) -> RepositoryGeneric[ServerTask, ServerTaskModel]:
return RepositoryGeneric(self.session, PK_ID, ServerTaskModel, ServerTask)
@cached_property
def groups(self) -> RepositoryGroup:
return RepositoryGroup(self.session, PK_ID, Group, GroupInDB)

View File

@ -8,7 +8,6 @@ from . import (
admin_maintenance,
admin_management_groups,
admin_management_users,
admin_server_tasks,
)
router = AdminAPIRouter(prefix="/admin")
@ -17,7 +16,6 @@ router.include_router(admin_about.router, tags=["Admin: About"])
router.include_router(admin_management_users.router, tags=["Admin: Manage Users"])
router.include_router(admin_management_groups.router, tags=["Admin: Manage Groups"])
router.include_router(admin_email.router, tags=["Admin: Email"])
router.include_router(admin_server_tasks.router, tags=["Admin: Server Tasks"])
router.include_router(admin_backups.router, tags=["Admin: Backups"])
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])
router.include_router(admin_analytics.router, tags=["Admin: Analytics"])

View File

@ -1,27 +0,0 @@
from fastapi import BackgroundTasks, Depends
from mealie.routes._base import BaseAdminController, controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.server.tasks import ServerTask, ServerTaskNames, ServerTaskPagination
from mealie.services.server_tasks import BackgroundExecutor, test_executor_func
router = UserAPIRouter()
@controller(router)
class AdminServerTasksController(BaseAdminController):
@router.get("/server-tasks", response_model=ServerTaskPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
response = self.repos.server_tasks.page_all(
pagination=q,
override=ServerTask,
)
response.set_pagination_guides(router.url_path_for("get_all"), q.model_dump())
return response
@router.post("/server-tasks", response_model=ServerTask, status_code=201)
def create_test_tasks(self, bg_tasks: BackgroundTasks):
bg_executor = BackgroundExecutor(self.group.id, self.repos, bg_tasks)
return bg_executor.dispatch(ServerTaskNames.default, test_executor_func)

View File

@ -1,10 +0,0 @@
# This file is auto-generated by gen_schema_exports.py
from .tasks import ServerTask, ServerTaskCreate, ServerTaskNames, ServerTaskPagination, ServerTaskStatus
__all__ = [
"ServerTask",
"ServerTaskCreate",
"ServerTaskNames",
"ServerTaskPagination",
"ServerTaskStatus",
]

View File

@ -1,50 +0,0 @@
import datetime
import enum
from uuid import UUID
from pydantic import ConfigDict, Field
from mealie.schema._mealie import MealieModel
from mealie.schema.response.pagination import PaginationBase
class ServerTaskNames(str, enum.Enum):
default = "Background Task"
backup_task = "Database Backup"
bulk_recipe_import = "Bulk Recipe Import"
class ServerTaskStatus(str, enum.Enum):
running = "running"
finished = "finished"
failed = "failed"
class ServerTaskCreate(MealieModel):
group_id: UUID
name: ServerTaskNames = ServerTaskNames.default
created_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
status: ServerTaskStatus = ServerTaskStatus.running
log: str = ""
def set_running(self) -> None:
self.status = ServerTaskStatus.running
def set_finished(self) -> None:
self.status = ServerTaskStatus.finished
def set_failed(self) -> None:
self.status = ServerTaskStatus.failed
def append_log(self, message: str) -> None:
# Prefix with Timestamp and append new line and join to log
self.log += f"{datetime.datetime.now()}: {message}\n"
class ServerTask(ServerTaskCreate):
id: int
model_config = ConfigDict(from_attributes=True)
class ServerTaskPagination(PaginationBase):
items: list[ServerTask]

View File

@ -1 +0,0 @@
from .background_executory import *

View File

@ -1,62 +0,0 @@
from collections.abc import Callable
from random import getrandbits
from time import sleep
from typing import Any
from fastapi import BackgroundTasks
from pydantic import UUID4
from sqlalchemy.orm import Session
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.server.tasks import ServerTask, ServerTaskCreate, ServerTaskNames
class BackgroundExecutor:
sleep_time = 60
def __init__(self, group_id: UUID4, repos: AllRepositories, bg: BackgroundTasks) -> None:
self.group_id = group_id
self.repos = repos
self.background_tasks = bg
def dispatch(self, task_name: ServerTaskNames, func: Callable, *args: Any, **kwargs: Any) -> ServerTask:
"""The dispatch function is a wrapper around the BackgroundTasks class in Starlett. It directly calls
the add_task function and your task will be run in the background. This function all passes the id required
to check on the server tasks in the database and provide updates.
Tasks that are dispachd by the Background executor should be designed to accept this key word argument
and update the item in the database accordingly.
"""
server_task = ServerTaskCreate(group_id=self.group_id, name=task_name)
server_task = self.repos.server_tasks.create(server_task)
self.background_tasks.add_task(func, *args, **kwargs, task_id=server_task.id, session=self.repos.session)
return server_task
def test_executor_func(task_id: int, session: Session) -> None:
database = get_repositories(session)
task = database.server_tasks.get_one(task_id)
task.append_log("test task has started")
task.append_log("test task sleeping for 60 seconds")
sleep(BackgroundExecutor.sleep_time)
task.append_log("test task has finished sleep")
# Randomly Decide to set to failed or not
is_fail = bool(getrandbits(1))
if is_fail:
task.append_log("test task has failed")
task.set_failed()
else:
task.append_log("test task has succeeded")
task.set_finished()
database.server_tasks.update(task.id, task)

View File

@ -1,21 +0,0 @@
from fastapi.testclient import TestClient
from mealie.services.server_tasks.background_executory import BackgroundExecutor
from tests.utils import api_routes
from tests.utils.fixture_schemas import TestUser
def test_admin_server_tasks_test_and_get(api_client: TestClient, admin_user: TestUser):
# Bootstrap Timer
BackgroundExecutor.sleep_time = 1
response = api_client.post(api_routes.admin_server_tasks, headers=admin_user.token)
assert response.status_code == 201
response = api_client.get(api_routes.admin_server_tasks, headers=admin_user.token)
as_dict = response.json()["items"]
assert len(as_dict) == 1
# Reset Timer
BackgroundExecutor.sleep_time = 60

View File

@ -33,8 +33,6 @@ admin_maintenance_logs = "/api/admin/maintenance/logs"
"""`/api/admin/maintenance/logs`"""
admin_maintenance_storage = "/api/admin/maintenance/storage"
"""`/api/admin/maintenance/storage`"""
admin_server_tasks = "/api/admin/server-tasks"
"""`/api/admin/server-tasks`"""
admin_users = "/api/admin/users"
"""`/api/admin/users`"""
admin_users_password_reset_token = "/api/admin/users/password-reset-token"