Update.
This commit is contained in:
		
							parent
							
								
									c56bf4fb49
								
							
						
					
					
						commit
						ee40c905d4
					
				@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								src/snek/mapper/repository.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/snek/mapper/repository.py
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
@ -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,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								src/snek/model/repository.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/snek/model/repository.py
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
@ -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),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								src/snek/service/repository.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/snek/service/repository.py
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
@ -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():
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								src/snek/templates/settings/repositories/create.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/snek/templates/settings/repositories/create.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
{% extends 'settings/index.html' %}
 | 
			
		||||
 | 
			
		||||
{% block header_text %}<h1><i class="fa-solid fa-plus"></i> Create Repository</h1>{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main %}
 | 
			
		||||
 | 
			
		||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
 | 
			
		||||
<style>
 | 
			
		||||
.container {
 | 
			
		||||
    div,input,label,button{
 | 
			
		||||
        padding-bottom: 15px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    form {
 | 
			
		||||
      padding: 2rem;
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    label { font-weight: bold; display: flex; align-items: center; gap: 0.5rem;}
 | 
			
		||||
    input[type="text"] {
 | 
			
		||||
      padding: 0.5rem;
 | 
			
		||||
      border: 1px solid #ccc; border-radius: 5px;
 | 
			
		||||
      font-size: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
button, a.button {
 | 
			
		||||
      background: #198754; color: #fff; border: none; border-radius: 5px;
 | 
			
		||||
      padding: 0.1rem 0.8rem; text-decoration: none; cursor: pointer;
 | 
			
		||||
      transition: background 0.2s;
 | 
			
		||||
      font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem;
 | 
			
		||||
    }
 | 
			
		||||
    .
 | 
			
		||||
    .cancel {
 | 
			
		||||
      background: #6c757d;
 | 
			
		||||
    }
 | 
			
		||||
    @media (max-width: 600px) {
 | 
			
		||||
      .container { max-width: 98vw; }
 | 
			
		||||
      form { padding: 1rem; }
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <form action="/settings/repositories/create.html" method="post">
 | 
			
		||||
      <div>
 | 
			
		||||
        <label for="name"><i class="fa-solid fa-book"></i> Name</label>
 | 
			
		||||
        <input type="text" id="name" name="name" required placeholder="Repository name">
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <label>
 | 
			
		||||
          <input type="checkbox" name="is_private" value="1">
 | 
			
		||||
          <i class="fa-solid fa-lock"></i> Private
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <button type="submit"><i class="fa-solid fa-plus"></i> Create</button>
 | 
			
		||||
      <button onclick="history.back()" class="cancel"><i class="fa-solid fa-arrow-left"></i> Back</button> 
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% endblock %}
 | 
			
		||||
							
								
								
									
										63
									
								
								src/snek/templates/settings/repositories/delete.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/snek/templates/settings/repositories/delete.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="UTF-8">
 | 
			
		||||
  <title>Delete Repository</title>
 | 
			
		||||
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
 | 
			
		||||
  <style>
 | 
			
		||||
    body { font-family: sans-serif; margin: 2rem; }
 | 
			
		||||
    .container { max-width: 400px; margin: 0 auto; }
 | 
			
		||||
    .confirm-box {
 | 
			
		||||
      background: #ffe5e8;
 | 
			
		||||
      border: 1.5px solid #dc3545;
 | 
			
		||||
      padding: 2rem;
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      margin-top: 2rem;
 | 
			
		||||
    }
 | 
			
		||||
    .repo-name {
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
      font-size: 1.2rem;
 | 
			
		||||
      margin: 1rem 0;
 | 
			
		||||
      color: #dc3545;
 | 
			
		||||
    }
 | 
			
		||||
    .actions {
 | 
			
		||||
      display: flex; gap: 1rem; justify-content: center; margin-top: 1.5rem;
 | 
			
		||||
    }
 | 
			
		||||
    button, a {
 | 
			
		||||
      background: #dc3545; color: #fff;
 | 
			
		||||
      border: none; border-radius: 5px; padding: 0.6rem 1.2rem;
 | 
			
		||||
      font-size: 1rem; cursor: pointer;
 | 
			
		||||
      display: flex; align-items: center; gap: 0.5rem; text-decoration: none; justify-content: center;
 | 
			
		||||
      transition: background 0.2s;
 | 
			
		||||
    }
 | 
			
		||||
    .cancel {
 | 
			
		||||
      background: #6c757d;
 | 
			
		||||
    }
 | 
			
		||||
    @media (max-width: 600px) {
 | 
			
		||||
      .container { max-width: 98vw; }
 | 
			
		||||
      .confirm-box { padding: 1rem; }
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <h1><i class="fa-solid fa-trash-can"></i> Delete Repository</h1>
 | 
			
		||||
    <div class="confirm-box">
 | 
			
		||||
      <div>
 | 
			
		||||
        <i class="fa-solid fa-triangle-exclamation" style="font-size:2rem; color:#dc3545;"></i>
 | 
			
		||||
      </div>
 | 
			
		||||
      <p>Are you sure you want to <strong>delete</strong> the following repository?</p>
 | 
			
		||||
      <div class="repo-name"><i class="fa-solid fa-book"></i> my-first-repo</div>
 | 
			
		||||
      <form action="/repositories/delete" method="post" style="margin-top:1.5rem;">
 | 
			
		||||
        <input type="hidden" name="id" value="1">
 | 
			
		||||
        <div class="actions">
 | 
			
		||||
          <button type="submit"><i class="fa-solid fa-trash"></i> Yes, delete</button>
 | 
			
		||||
          <a href="repositories.html" class="cancel"><i class="fa-solid fa-ban"></i> Cancel</a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										106
									
								
								src/snek/templates/settings/repositories/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/snek/templates/settings/repositories/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
			
		||||
{% extends 'settings/index.html' %}
 | 
			
		||||
 | 
			
		||||
{% block header_text %}<h1><i class="fa-solid fa-database"></i> Repositories</h1>{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main %}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="UTF-8">
 | 
			
		||||
  <title>Repositories - List</title>
 | 
			
		||||
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
 | 
			
		||||
  <style>
 | 
			
		||||
    .actions {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      gap: 0.5rem;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      flex-wrap: wrap;
 | 
			
		||||
    }
 | 
			
		||||
    .repo-list {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      gap: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
    .repo-row {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      padding: 1rem;
 | 
			
		||||
      border-radius: 8px;
 | 
			
		||||
      flex-wrap: wrap;
 | 
			
		||||
    }
 | 
			
		||||
    .repo-info {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      gap: 1rem;
 | 
			
		||||
      flex: 1;
 | 
			
		||||
      min-width: 220px;
 | 
			
		||||
    }
 | 
			
		||||
    .repo-name {
 | 
			
		||||
      font-size: 1.1rem;
 | 
			
		||||
      font-weight: 600;
 | 
			
		||||
    }
 | 
			
		||||
    @media (max-width: 600px) {
 | 
			
		||||
      .repo-row { flex-direction: column; align-items: stretch; }
 | 
			
		||||
      .actions { justify-content: flex-start; }
 | 
			
		||||
    }
 | 
			
		||||
    .topbar {
 | 
			
		||||
      display: flex;
 | 
			
		||||
    /*  justify-content: flex-end;*/
 | 
			
		||||
      margin-bottom: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
    button, a.button {
 | 
			
		||||
      background: #198754; color: #fff; border: none; border-radius: 5px;
 | 
			
		||||
      padding: 0.4rem 0.8rem; text-decoration: none; cursor: pointer;
 | 
			
		||||
      transition: background 0.2s;
 | 
			
		||||
      font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem;
 | 
			
		||||
    }
 | 
			
		||||
    .button.delete { background: #dc3545; }
 | 
			
		||||
    .button.edit { background: #0d6efd; }
 | 
			
		||||
    .button.clone { background: #6c757d; }
 | 
			
		||||
    .button.browse { background: #ffc107; color: #212529; }
 | 
			
		||||
    .button.create { background: #20c997; margin-left: 0.5rem; }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
      
 | 
			
		||||
    <div class="topbar">
 | 
			
		||||
      <a class="button create" href="/settings/repositories/create.html">
 | 
			
		||||
        <i class="fa-solid fa-plus"></i> New Repository
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
    <section class="repo-list">
 | 
			
		||||
      <!-- Example repository entries; replace with your templating/iteration -->
 | 
			
		||||
      {% for repo in repositories %}
 | 
			
		||||
      <div class="repo-row">
 | 
			
		||||
        <div class="repo-info">
 | 
			
		||||
            <span class="repo-name"><i class="fa-solid fa-book"></i> {{ repo.name }}</span>
 | 
			
		||||
        
 | 
			
		||||
<span title="Public">
 | 
			
		||||
    <i class="fa-solid {% if repo.is_private %}fa-lock{% else %}fa-lock-open{% endif %}"></i>
 | 
			
		||||
    {% if repo.is_private %}Private{% else %}Public{% endif %}
 | 
			
		||||
</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="actions">
 | 
			
		||||
            <a class="button browse" href="/repositories/{{ user.username }}/{{ repo.name }}" target="_blank">
 | 
			
		||||
            <i class="fa-solid fa-folder-open"></i> Browse
 | 
			
		||||
          </a>
 | 
			
		||||
          <a class="button clone" href="/repositories/{{ user.username }}/{{ repo.name }}/clone">
 | 
			
		||||
            <i class="fa-solid fa-code-branch"></i> Clone
 | 
			
		||||
          </a>
 | 
			
		||||
          <a class="button edit" href="/settings/repositories/repository/{{ repo.name }}/update.html">
 | 
			
		||||
            <i class="fa-solid fa-pen"></i> Edit
 | 
			
		||||
          </a>
 | 
			
		||||
          <a class="button delete" href="/settings/repositories/{{ repo.name }}/delete.html">
 | 
			
		||||
            <i class="fa-solid fa-trash"></i> Delete
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {% endfor %}
 | 
			
		||||
      <!-- ... -->
 | 
			
		||||
    </section>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										45
									
								
								src/snek/templates/settings/repositories/update.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/snek/templates/settings/repositories/update.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
			
		||||
{% extends "settings/index.html" %}
 | 
			
		||||
 | 
			
		||||
{% block header_text %}<h1><i class="fa-solid fa-pen"></i> Update Repository</h1>{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main %}
 | 
			
		||||
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
 | 
			
		||||
  <style>
 | 
			
		||||
    form {
 | 
			
		||||
      padding: 2rem;
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    label { font-weight: bold; display: flex; align-items: center; gap: 0.5rem;}
 | 
			
		||||
    button {
 | 
			
		||||
      background: #0d6efd; color: #fff;
 | 
			
		||||
      border: none; border-radius: 5px; padding: 0.6rem 1rem;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem;
 | 
			
		||||
    }
 | 
			
		||||
    .cancel {
 | 
			
		||||
      background: #6c757d;
 | 
			
		||||
    }
 | 
			
		||||
      @media (max-width: 600px) {
 | 
			
		||||
      .container { max-width: 98vw; }
 | 
			
		||||
      form { padding: 1rem; }
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <form method="post">
 | 
			
		||||
      <!-- Assume hidden id for backend use -->
 | 
			
		||||
      <input type="hidden" name="id" value="{{ repository.id }}">
 | 
			
		||||
      <div>
 | 
			
		||||
        <label for="name"><i class="fa-solid fa-book"></i> Name</label>
 | 
			
		||||
        <input type="text" id="name" name="name" value="{{ repository.name }}" readonly>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <label>
 | 
			
		||||
            <input type="checkbox" name="is_private" value="1" {% if repository.is_private %}checked{% endif %}>
 | 
			
		||||
            <i class="fa-solid fa-lock"></i> Private
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <button type="submit"><i class="fa-solid fa-pen"></i> Update</button>
 | 
			
		||||
      <button onclick="history.back()" class="cancel"><i class="fa-solid fa-arrow-left"></i>Cancel</button>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    <h2>You</h2>
 | 
			
		||||
    <ul>
 | 
			
		||||
        <li><a class="no-select" href="/settings/profile.html">Profile</a></li>
 | 
			
		||||
        <li><a class="no-select" href="/settings/gists.html">Gists</a></li>
 | 
			
		||||
        <li><a class="no-select" href="/settings/repositories/index.html">Repositories</a></li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
  </aside>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										68
									
								
								src/snek/view/settings/repositories.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/snek/view/settings/repositories.py
									
									
									
									
									
										Normal file
									
								
							@ -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")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user