diff --git a/src/snek/__main__.py b/src/snek/__main__.py index c4996aa..198ea1e 100644 --- a/src/snek/__main__.py +++ b/src/snek/__main__.py @@ -1,11 +1,14 @@ import argparse - +import uvloop from aiohttp import web - +import asyncio from snek.app import Application def main(): + + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + parser = argparse.ArgumentParser(description="Run the web application.") parser.add_argument( "--port", diff --git a/src/snek/app.py b/src/snek/app.py index 605934a..0b5e14b 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -39,6 +39,10 @@ from snek.view.logout import LogoutView from snek.view.register import RegisterView from snek.view.rpc import RPCView from snek.view.search_user import SearchUserView +from snek.view.settings.repositories import RepositoriesIndexView +from snek.view.settings.repositories import RepositoriesCreateView +from snek.view.settings.repositories import RepositoriesUpdateView +from snek.view.settings.repositories import RepositoriesDeleteView from snek.view.settings.index import SettingsIndexView from snek.view.settings.profile import SettingsProfileView from snek.view.stats import StatsView @@ -175,6 +179,10 @@ class Application(BaseApplication): self.router.add_view("/drive/{drive}.json", DriveView) self.router.add_view("/stats.json", StatsView) self.router.add_view("/user/{user}.html", UserView) + self.router.add_view("/settings/repositories/index.html", RepositoriesIndexView) + self.router.add_view("/settings/repositories/create.html", RepositoriesCreateView) + self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView) + self.router.add_view("/settings/repositories/respository/{name}/delete.html", RepositoriesDeleteView) self.webdav = WebdavApplication(self) self.add_subapp("/webdav", self.webdav) diff --git a/src/snek/mapper/__init__.py b/src/snek/mapper/__init__.py index 2d4b12c..ab7904f 100644 --- a/src/snek/mapper/__init__.py +++ b/src/snek/mapper/__init__.py @@ -8,6 +8,7 @@ from snek.mapper.drive_item import DriveItemMapper from snek.mapper.notification import NotificationMapper from snek.mapper.user import UserMapper from snek.mapper.user_property import UserPropertyMapper +from snek.mapper.repository import RepositoryMapper from snek.system.object import Object @@ -23,6 +24,7 @@ def get_mappers(app=None): "drive_item": DriveItemMapper(app=app), "drive": DriveMapper(app=app), "user_property": UserPropertyMapper(app=app), + "repository": RepositoryMapper(app=app), } ) diff --git a/src/snek/mapper/repository.py b/src/snek/mapper/repository.py new file mode 100644 index 0000000..1ac10d4 --- /dev/null +++ b/src/snek/mapper/repository.py @@ -0,0 +1,7 @@ +from snek.model.repository import RepositoryModel +from snek.system.mapper import BaseMapper + + +class RepositoryMapper(BaseMapper): + model_class = RepositoryModel + table_name = "repository" diff --git a/src/snek/model/__init__.py b/src/snek/model/__init__.py index a1009a5..6399c89 100644 --- a/src/snek/model/__init__.py +++ b/src/snek/model/__init__.py @@ -10,6 +10,7 @@ from snek.model.drive_item import DriveItemModel from snek.model.notification import NotificationModel from snek.model.user import UserModel from snek.model.user_property import UserPropertyModel +from snek.model.repository import RepositoryModel from snek.system.object import Object @@ -25,6 +26,7 @@ def get_models(): "drive": DriveModel, "notification": NotificationModel, "user_property": UserPropertyModel, + "repository": RepositoryModel, } ) diff --git a/src/snek/model/repository.py b/src/snek/model/repository.py new file mode 100644 index 0000000..598cbb2 --- /dev/null +++ b/src/snek/model/repository.py @@ -0,0 +1,14 @@ +from snek.model.user import UserModel +from snek.system.model import BaseModel, ModelField + + +class RepositoryModel(BaseModel): + + user_uid = ModelField(name="user_uid", required=True, kind=str) + + name = ModelField(name="name", required=True, kind=str) + + is_private = ModelField(name="is_private", required=False, kind=bool) + + + diff --git a/src/snek/service/__init__.py b/src/snek/service/__init__.py index 3ec4592..be356dc 100644 --- a/src/snek/service/__init__.py +++ b/src/snek/service/__init__.py @@ -11,6 +11,7 @@ from snek.service.socket import SocketService from snek.service.user import UserService from snek.service.user_property import UserPropertyService from snek.service.util import UtilService +from snek.service.repository import RepositoryService from snek.system.object import Object @@ -29,6 +30,7 @@ def get_services(app): "drive": DriveService(app=app), "drive_item": DriveItemService(app=app), "user_property": UserPropertyService(app=app), + "repository": RepositoryService(app=app), } ) diff --git a/src/snek/service/repository.py b/src/snek/service/repository.py new file mode 100644 index 0000000..f7694d7 --- /dev/null +++ b/src/snek/service/repository.py @@ -0,0 +1,39 @@ +from snek.system.service import BaseService +import asyncio + +class RepositoryService(BaseService): + mapper_name = "repository" + + async def exists(self, user_uid, name, **kwargs): + kwargs["user_uid"] = user_uid + kwargs["name"] = name + return await self.exists(**kwargs) + + async def init(self, user_uid, name): + repository_path = await self.services.user.get_repository_path(user_uid) + if not repository_path.exists(): + repository_path.mkdir(parents=True) + repository_path = repository_path.joinpath(name) + command = ['git', 'init', '--bare', repository_path] + process = await asyncio.subprocess.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + if process.returncode == 0: + print(f"Bare Git repository created at: {repo_path}") + else: + print(f"Error creating repository: {stderr.decode().strip()}") + + async def create(self, user_uid, name,is_private=False): + if await self.exists(user_uid=user_uid, name=name): + return False + + + + model = await self.new() + model["user_uid"] = user_uid + model["name"] = name + model["is_private"] = is_private + return await self.save(model) diff --git a/src/snek/service/user.py b/src/snek/service/user.py index fb66ddb..7ca0711 100644 --- a/src/snek/service/user.py +++ b/src/snek/service/user.py @@ -42,6 +42,12 @@ class UserService(BaseService): def get_admin_uids(self): return self.mapper.get_admin_uids() + async def get_repository_path(self, user_uid): + path = pathlib.Path(f"./drive/repositories/{user_uid}") + if not path.exists(): + return None + return path + async def get_static_path(self, user_uid): path = pathlib.Path(f"./drive/{user_uid}/snek/static") if not path.exists(): diff --git a/src/snek/templates/settings/repositories/create.html b/src/snek/templates/settings/repositories/create.html new file mode 100644 index 0000000..cfef080 --- /dev/null +++ b/src/snek/templates/settings/repositories/create.html @@ -0,0 +1,59 @@ +{% extends 'settings/index.html' %} + +{% block header_text %}

Create Repository

{% endblock %} + +{% block main %} + + + + + +
+
+
+ + +
+
+ +
+ + +
+
+ {% endblock %} diff --git a/src/snek/templates/settings/repositories/delete.html b/src/snek/templates/settings/repositories/delete.html new file mode 100644 index 0000000..af2a906 --- /dev/null +++ b/src/snek/templates/settings/repositories/delete.html @@ -0,0 +1,63 @@ + + + + + Delete Repository + + + + +
+

Delete Repository

+
+
+ +
+

Are you sure you want to delete the following repository?

+
my-first-repo
+
+ +
+ + Cancel +
+
+
+
+ + + diff --git a/src/snek/templates/settings/repositories/index.html b/src/snek/templates/settings/repositories/index.html new file mode 100644 index 0000000..a160736 --- /dev/null +++ b/src/snek/templates/settings/repositories/index.html @@ -0,0 +1,106 @@ +{% extends 'settings/index.html' %} + +{% block header_text %}

Repositories

{% endblock %} + +{% block main %} + + + + + Repositories - List + + + + +
+ +
+ + New Repository + +
+
+ + {% for repo in repositories %} +
+
+ {{ repo.name }} + + + + {% if repo.is_private %}Private{% else %}Public{% endif %} + +
+ +
+ {% endfor %} + +
+
+ + +{% endblock %} diff --git a/src/snek/templates/settings/repositories/update.html b/src/snek/templates/settings/repositories/update.html new file mode 100644 index 0000000..5168c92 --- /dev/null +++ b/src/snek/templates/settings/repositories/update.html @@ -0,0 +1,45 @@ +{% extends "settings/index.html" %} + +{% block header_text %}

Update Repository

{% endblock %} + +{% block main %} + + +
+
+ + +
+ + +
+
+ +
+ + +
+
+{% endblock %} diff --git a/src/snek/templates/settings/sidebar.html b/src/snek/templates/settings/sidebar.html index f9533be..9673b6e 100644 --- a/src/snek/templates/settings/sidebar.html +++ b/src/snek/templates/settings/sidebar.html @@ -3,7 +3,7 @@

You

diff --git a/src/snek/view/settings/repositories.py b/src/snek/view/settings/repositories.py new file mode 100644 index 0000000..1b652e6 --- /dev/null +++ b/src/snek/view/settings/repositories.py @@ -0,0 +1,68 @@ +import asyncio +from aiohttp import web + +from snek.system.view import BaseFormView +import pathlib + +class RepositoriesIndexView(BaseFormView): + + login_required = True + + async def get(self): + + user_uid = self.session.get("uid") + + repositories = [] + async for repository in self.services.repository.find(user_uid=user_uid): + repositories.append(repository.record) + + return await self.render_template("settings/repositories/index.html", {"repositories": repositories}) + + + + +class RepositoriesCreateView(BaseFormView): + + login_required = True + + async def get(self): + + return await self.render_template("settings/repositories/create.html") + + async def post(self): + data = await self.request.post() + repository = await self.services.repository.create(user_uid=self.session.get("uid"), name=data['name'], is_private=int(data.get('is_private',0))) + return web.HTTPFound("/settings/repositories/index.html") + +class RepositoriesUpdateView(BaseFormView): + + login_required = True + + async def get(self): + + repository = await self.services.repository.get( + user_uid=self.session.get("uid"), name=self.request.match_info["name"] + ) + if not repository: + return web.HTTPNotFound() + return await self.render_template("settings/repositories/update.html", {"repository": repository.record}) + + async def post(self): + data = await self.request.post() + repository = await self.services.repository.get( + user_uid=self.session.get("uid"), name=self.request.match_info["name"] + ) + repository['is_private'] = int(data.get('is_private',0)) + await self.services.repository.save(repository) + return web.HTTPFound("/settings/repositories/index.html") + +class RepositoriesDeleteView(BaseFormView): + + login_required = True + + async def get(self): + + return await self.render_template("settings/repositories/delete.html") + + +