mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-30 17:47:24 +01:00
Compare commits
7 Commits
mk/multi-g
...
copilot/au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bc6b4519c | ||
|
|
6f77eae638 | ||
|
|
6926aabd62 | ||
|
|
b735ad12fd | ||
|
|
f3e817e139 | ||
|
|
153ecd1094 | ||
|
|
0be54da9cf |
349
.github/scripts/update_language_names.py
vendored
Executable file
349
.github/scripts/update_language_names.py
vendored
Executable file
@@ -0,0 +1,349 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to automatically update language names in the English translation file.
|
||||
Queries Weblate for translation completion and language names.
|
||||
Only adds languages with >=80% completion to en.json.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import requests
|
||||
from babel import Locale, UnknownLocaleError
|
||||
|
||||
LOCALES_DIR = Path('frontend/locales')
|
||||
EN_JSON_PATH = LOCALES_DIR / 'en.json'
|
||||
WEBLATE_API_URL = 'https://translate.sysadminsmedia.com/api'
|
||||
WEBLATE_PROJECT = 'homebox'
|
||||
WEBLATE_COMPONENT = 'frontend'
|
||||
COMPLETION_THRESHOLD = 80.0 # Minimum completion percentage to include language
|
||||
TIMEOUT = 10 # seconds
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s: %(message)s'
|
||||
)
|
||||
|
||||
|
||||
def get_locale_files() -> List[str]:
|
||||
"""Get all locale codes from JSON files in the locales directory."""
|
||||
if not LOCALES_DIR.exists():
|
||||
logging.error("Locales directory not found: %s", LOCALES_DIR)
|
||||
return []
|
||||
|
||||
locale_codes = []
|
||||
for file in sorted(LOCALES_DIR.glob('*.json')):
|
||||
# Extract locale code from filename (e.g., "en.json" -> "en")
|
||||
locale_code = file.stem
|
||||
# Validate locale code format - should not contain dots
|
||||
if '.' not in locale_code:
|
||||
locale_codes.append(locale_code)
|
||||
else:
|
||||
logging.warning("Skipping invalid locale code: %s", locale_code)
|
||||
|
||||
logging.info("Found %d locale files", len(locale_codes))
|
||||
return sorted(locale_codes)
|
||||
|
||||
|
||||
def fetch_weblate_translations() -> Optional[Dict[str, Dict]]:
|
||||
"""
|
||||
Fetch translation statistics from Weblate API.
|
||||
|
||||
Returns:
|
||||
Dict mapping locale code to translation data (percent, name, native_name)
|
||||
or None if API is unavailable
|
||||
"""
|
||||
url = f"{WEBLATE_API_URL}/components/{WEBLATE_PROJECT}/{WEBLATE_COMPONENT}/translations/"
|
||||
|
||||
try:
|
||||
# Weblate API may require pagination
|
||||
translations = {}
|
||||
page_url = url
|
||||
|
||||
while page_url:
|
||||
logging.info("Fetching translations from Weblate: %s", page_url)
|
||||
resp = requests.get(page_url, timeout=TIMEOUT)
|
||||
|
||||
if resp.status_code != 200:
|
||||
logging.warning("Weblate API returned status %d", resp.status_code)
|
||||
return None
|
||||
|
||||
data = resp.json()
|
||||
|
||||
for trans in data.get('results', []):
|
||||
# Weblate uses underscores, we use hyphens
|
||||
locale_code = trans.get('language_code', '').replace('_', '-')
|
||||
percent = trans.get('translated_percent', 0.0)
|
||||
|
||||
lang_info = trans.get('language', {})
|
||||
english_name = lang_info.get('name', '')
|
||||
native_name = lang_info.get('native', '')
|
||||
|
||||
translations[locale_code] = {
|
||||
'percent': percent,
|
||||
'english_name': english_name,
|
||||
'native_name': native_name
|
||||
}
|
||||
|
||||
# Check for next page
|
||||
page_url = data.get('next')
|
||||
|
||||
logging.info("Fetched %d translations from Weblate", len(translations))
|
||||
return translations
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.warning("Failed to fetch from Weblate API: %s", e)
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error("Unexpected error fetching Weblate data: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
def get_language_name_from_babel(locale_code: str) -> Optional[str]:
|
||||
"""
|
||||
Get the language name using Babel in format "English (Native)".
|
||||
Special handling for variants that need disambiguation (Portuguese, Chinese).
|
||||
|
||||
Args:
|
||||
locale_code: Language/locale code (e.g., 'en', 'pt-BR', 'zh-CN')
|
||||
|
||||
Returns:
|
||||
Language name in format "English (Native)" or None if cannot parse
|
||||
"""
|
||||
try:
|
||||
# Special handling for ar-AA (non-standard code, use standard 'ar')
|
||||
if locale_code == 'ar-AA':
|
||||
locale = Locale.parse('ar')
|
||||
else:
|
||||
# Parse locale code using Babel
|
||||
locale = Locale.parse(locale_code.replace('-', '_'))
|
||||
|
||||
# Get English display name
|
||||
english_name = locale.get_display_name('en')
|
||||
|
||||
# Get native display name
|
||||
native_name = locale.get_display_name(locale)
|
||||
|
||||
if not english_name:
|
||||
return None
|
||||
|
||||
# Special handling for Portuguese variants (distinguish Brazil vs Portugal)
|
||||
if locale_code == 'pt-BR':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Portuguese — Brazil ({native_base})"
|
||||
elif locale_code == 'pt-PT':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Portuguese — Portugal ({native_base})"
|
||||
|
||||
# Special handling for Chinese variants (distinguish Simplified/Traditional and regions)
|
||||
if locale_code == 'zh-CN':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Simplified ({native_base})"
|
||||
elif locale_code == 'zh-TW':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Traditional ({native_base})"
|
||||
elif locale_code == 'zh-HK':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Hong Kong ({native_base})"
|
||||
elif locale_code == 'zh-MO':
|
||||
native_base = native_name.split('(')[0].strip() if '(' in native_name else native_name
|
||||
return f"Chinese — Macau ({native_base})"
|
||||
|
||||
# Format: "English (Native)" if native name differs and is available
|
||||
if native_name and native_name != english_name:
|
||||
# Clean up nested parentheses for complex locales
|
||||
if '(' in english_name and '(' in native_name:
|
||||
# For cases like "Japanese (Japan) (日本語 (日本))"
|
||||
# Simplify to "Japanese (日本語)"
|
||||
english_base = english_name.split('(')[0].strip()
|
||||
native_base = native_name.split('(')[0].strip()
|
||||
return f"{english_base} ({native_base})"
|
||||
else:
|
||||
return f"{english_name} ({native_name})"
|
||||
else:
|
||||
return english_name
|
||||
|
||||
except (UnknownLocaleError, ValueError) as e:
|
||||
logging.debug("Could not parse locale '%s' with Babel: %s", locale_code, e)
|
||||
return None
|
||||
|
||||
|
||||
def get_language_name(locale_code: str, weblate_data: Optional[Dict] = None) -> Optional[str]:
|
||||
"""
|
||||
Get the display name for a locale code.
|
||||
Priority: Weblate API > Babel > None
|
||||
|
||||
Args:
|
||||
locale_code: Language/locale code (e.g., 'en', 'pt-BR', 'zh-CN')
|
||||
weblate_data: Translation data from Weblate (if available)
|
||||
|
||||
Returns:
|
||||
Language name in format "English (Native)" or None if invalid
|
||||
"""
|
||||
# Validate locale code format
|
||||
if '.' in locale_code or locale_code.startswith('languages.'):
|
||||
logging.error("Invalid locale code format: %s", locale_code)
|
||||
return None
|
||||
|
||||
# Try Weblate first
|
||||
if weblate_data and locale_code in weblate_data:
|
||||
english_name = weblate_data[locale_code].get('english_name', '')
|
||||
native_name = weblate_data[locale_code].get('native_name', '')
|
||||
|
||||
if english_name:
|
||||
# Format: "English (Native)" if both names available and different
|
||||
if native_name and native_name != english_name:
|
||||
return f"{english_name} ({native_name})"
|
||||
else:
|
||||
return english_name
|
||||
|
||||
# Fallback to Babel
|
||||
babel_name = get_language_name_from_babel(locale_code)
|
||||
if babel_name:
|
||||
return babel_name
|
||||
|
||||
# If all else fails, return None (don't guess)
|
||||
logging.warning("Could not determine language name for: %s", locale_code)
|
||||
return None
|
||||
|
||||
|
||||
def load_en_json() -> dict:
|
||||
"""Load the English translation JSON file."""
|
||||
if not EN_JSON_PATH.exists():
|
||||
logging.error("English translation file not found: %s", EN_JSON_PATH)
|
||||
return {}
|
||||
|
||||
try:
|
||||
with EN_JSON_PATH.open('r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (IOError, json.JSONDecodeError) as e:
|
||||
logging.error("Failed to load %s: %s", EN_JSON_PATH, e)
|
||||
return {}
|
||||
|
||||
|
||||
def save_en_json(data: dict):
|
||||
"""Save the English translation JSON file."""
|
||||
try:
|
||||
with EN_JSON_PATH.open('w', encoding='utf-8') as f:
|
||||
# Use 4-space indentation to match existing file format
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
# Add newline at end of file
|
||||
f.write('\n')
|
||||
logging.info("Saved updated en.json")
|
||||
except IOError as e:
|
||||
logging.error("Failed to save %s: %s", EN_JSON_PATH, e)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def update_language_names(en_data: dict, locale_codes: List[str], weblate_data: Optional[Dict] = None) -> bool:
|
||||
"""
|
||||
Update the languages section in en.json.
|
||||
- Add new languages with >=80% completion (from Weblate) or that exist as locale files
|
||||
- Never remove existing entries (even if completion drops below 80%)
|
||||
|
||||
Args:
|
||||
en_data: The parsed en.json data
|
||||
locale_codes: List of all locale codes from files
|
||||
weblate_data: Translation data from Weblate (if available)
|
||||
|
||||
Returns:
|
||||
True if changes were made, False otherwise
|
||||
"""
|
||||
# Ensure languages section exists
|
||||
if 'languages' not in en_data:
|
||||
en_data['languages'] = {}
|
||||
logging.info("Created 'languages' section in en.json")
|
||||
|
||||
languages = en_data['languages']
|
||||
original_languages = languages.copy()
|
||||
|
||||
# Process each locale file
|
||||
added_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for locale_code in locale_codes:
|
||||
# Skip if already in languages (never remove existing entries)
|
||||
if locale_code in languages:
|
||||
continue
|
||||
|
||||
# Check Weblate completion threshold if data available
|
||||
if weblate_data and locale_code in weblate_data:
|
||||
percent = weblate_data[locale_code].get('percent', 0.0)
|
||||
|
||||
if percent < COMPLETION_THRESHOLD:
|
||||
logging.info("Skipping %s: %.1f%% completion (threshold: %.1f%%)",
|
||||
locale_code, percent, COMPLETION_THRESHOLD)
|
||||
skipped_count += 1
|
||||
continue
|
||||
else:
|
||||
logging.info("Including %s: %.1f%% completion", locale_code, percent)
|
||||
else:
|
||||
# If Weblate data not available, include locale file but log warning
|
||||
logging.info("Including %s: Weblate data not available, locale file exists", locale_code)
|
||||
|
||||
# Get language name
|
||||
language_name = get_language_name(locale_code, weblate_data)
|
||||
|
||||
if language_name:
|
||||
languages[locale_code] = language_name
|
||||
logging.info("Added language: %s = %s", locale_code, language_name)
|
||||
added_count += 1
|
||||
else:
|
||||
logging.warning("Skipping %s: could not determine language name", locale_code)
|
||||
skipped_count += 1
|
||||
|
||||
# Sort languages alphabetically by key
|
||||
en_data['languages'] = dict(sorted(languages.items()))
|
||||
|
||||
# Check if anything changed
|
||||
changed = (original_languages != en_data['languages'])
|
||||
|
||||
if changed:
|
||||
logging.info("Updated %d language names, skipped %d", added_count, skipped_count)
|
||||
else:
|
||||
logging.info("All languages already present, no changes needed")
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def main():
|
||||
setup_logging()
|
||||
logging.info("🔄 Starting language names update")
|
||||
|
||||
# Get all locale files
|
||||
locale_codes = get_locale_files()
|
||||
if not locale_codes:
|
||||
logging.error("No locale files found")
|
||||
sys.exit(1)
|
||||
|
||||
# Load English translation file
|
||||
en_data = load_en_json()
|
||||
if not en_data:
|
||||
logging.error("Failed to load English translation file")
|
||||
sys.exit(1)
|
||||
|
||||
# Fetch Weblate translation statistics
|
||||
weblate_data = fetch_weblate_translations()
|
||||
if weblate_data:
|
||||
logging.info("Successfully fetched Weblate data for %d languages", len(weblate_data))
|
||||
else:
|
||||
logging.warning("Weblate data not available, proceeding with locale files only")
|
||||
|
||||
# Update language names
|
||||
changed = update_language_names(en_data, locale_codes, weblate_data)
|
||||
|
||||
if changed:
|
||||
save_en_json(en_data)
|
||||
logging.info("✅ Language names updated successfully")
|
||||
else:
|
||||
logging.info("✅ No updates needed, en.json is already up-to-date")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
70
.github/workflows/update-language-names.yml
vendored
Normal file
70
.github/workflows/update-language-names.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Update Language Names
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'frontend/locales/*.json'
|
||||
- '.github/scripts/update_language_names.py'
|
||||
- '.github/workflows/update-language-names.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-language-names:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
|
||||
with:
|
||||
python-version: '3.8'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: .github/workflows/update-languages/requirements.txt
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r .github/workflows/update-languages/requirements.txt
|
||||
|
||||
- name: Run language names update script
|
||||
run: python .github/scripts/update_language_names.py
|
||||
|
||||
- name: Check for en.json changes
|
||||
run: |
|
||||
if git diff --quiet -- frontend/locales/en.json; then
|
||||
echo "changed=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "changed=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: env.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: automation/update-language-file
|
||||
base: main
|
||||
title: "Update language names in en.json"
|
||||
commit-message: "chore: update language names in en.json"
|
||||
body: |
|
||||
This PR automatically updates the language names in `frontend/locales/en.json` based on the available locale files.
|
||||
|
||||
New languages have been added to ensure all locale files have corresponding language names in the English translation file.
|
||||
|
||||
🤖 This PR was automatically created by the update-language-names workflow.
|
||||
path: .
|
||||
add-paths: |
|
||||
frontend/locales/en.json
|
||||
|
||||
- name: No updates needed
|
||||
if: env.changed == 'false'
|
||||
run: echo "✅ en.json language names are already up-to-date"
|
||||
2
.github/workflows/update-languages/requirements.txt
vendored
Normal file
2
.github/workflows/update-languages/requirements.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
babel
|
||||
requests
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -67,3 +67,5 @@ frontend/test-results/
|
||||
frontend/playwright-report/
|
||||
frontend/blob-report/
|
||||
frontend/playwright/.cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@@ -8,14 +8,13 @@ import (
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
|
||||
)
|
||||
|
||||
func (a *app) SetupDemo() error {
|
||||
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
|
||||
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
|
||||
,Office,IOT; Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
|
||||
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
|
||||
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
|
||||
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
|
||||
@@ -30,26 +29,21 @@ func (a *app) SetupDemo() error {
|
||||
Password: "demo",
|
||||
}
|
||||
|
||||
// If demo user already exists, skip all demo seeding tasks
|
||||
if a.services.User.ExistsByEmail(ctx, registration.Email) {
|
||||
log.Info().Msg("Demo user already exists; skipping demo seeding")
|
||||
// First check if we've already setup a demo user and skip if so
|
||||
log.Debug().Msg("Checking if demo user already exists")
|
||||
_, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
|
||||
if err == nil {
|
||||
log.Info().Msg("Demo user already exists, skipping setup")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, register the demo user
|
||||
log.Debug().Msg("Registering demo user")
|
||||
_, err := a.services.User.RegisterUser(ctx, registration)
|
||||
log.Debug().Msg("Demo user does not exist, setting up demo")
|
||||
_, err = a.services.User.RegisterUser(ctx, registration)
|
||||
if err != nil {
|
||||
if ent.IsConstraintError(err) {
|
||||
// Concurrent creation race: treat as exists and skip
|
||||
log.Info().Msg("Demo user concurrently created; skipping seeding")
|
||||
return nil
|
||||
}
|
||||
log.Err(err).Msg("Failed to register demo user")
|
||||
return errors.New("failed to setup demo")
|
||||
}
|
||||
|
||||
// Login the demo user to get a token
|
||||
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to login demo user")
|
||||
@@ -61,7 +55,7 @@ func (a *app) SetupDemo() error {
|
||||
return errors.New("failed to setup demo")
|
||||
}
|
||||
|
||||
_, err = a.services.Items.CsvImport(ctx, self.DefaultGroupID, strings.NewReader(csvText))
|
||||
_, err = a.services.Items.CsvImport(ctx, self.GroupID, strings.NewReader(csvText))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to import CSV")
|
||||
return errors.New("failed to setup demo")
|
||||
|
||||
@@ -119,14 +119,14 @@ func (ctrl *V1Controller) HandleWipeInventory() errchain.HandlerFunc {
|
||||
if ctrl.isDemo {
|
||||
return validate.NewRequestError(errors.New("wipe inventory is not allowed in demo mode"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
|
||||
// Check if user is owner
|
||||
if !ctx.User.IsOwner {
|
||||
return validate.NewRequestError(errors.New("only group owners can wipe inventory"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
|
||||
// Parse options from request body
|
||||
var options WipeInventoryOptions
|
||||
if err := server.Decode(r, &options); err != nil {
|
||||
@@ -137,13 +137,13 @@ func (ctrl *V1Controller) HandleWipeInventory() errchain.HandlerFunc {
|
||||
WipeMaintenance: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
totalCompleted, err := ctrl.repo.Items.WipeInventory(ctx, ctx.GID, options.WipeLabels, options.WipeLocations, options.WipeMaintenance)
|
||||
if err != nil {
|
||||
log.Err(err).Str("action_ref", "wipe inventory").Msg("failed to run action")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
||||
// Publish mutation events for wiped resources
|
||||
if ctrl.bus != nil {
|
||||
if options.WipeLabels {
|
||||
@@ -153,7 +153,7 @@ func (ctrl *V1Controller) HandleWipeInventory() errchain.HandlerFunc {
|
||||
ctrl.bus.Publish(eventbus.EventLocationMutation, eventbus.GroupMutationEvent{GID: ctx.GID})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return server.JSON(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
@@ -23,10 +22,6 @@ type (
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
Uses int `json:"uses"`
|
||||
}
|
||||
|
||||
GroupMemberAdd struct {
|
||||
UserID uuid.UUID `json:"userId" validate:"required"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleGroupGet godoc
|
||||
@@ -100,132 +95,3 @@ func (ctrl *V1Controller) HandleGroupInvitationsCreate() errchain.HandlerFunc {
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleGroupsGetAll godoc
|
||||
//
|
||||
// @Summary Get All Groups
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.Group
|
||||
// @Router /v1/groups [Get]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupsGetAll() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.Group, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Groups.GetAllGroups(auth)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupCreate godoc
|
||||
//
|
||||
// @Summary Create Group
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Param name body string true "Group Name"
|
||||
// @Success 201 {object} repo.Group
|
||||
// @Router /v1/groups/{id} [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupCreate() errchain.HandlerFunc {
|
||||
type CreateRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
fn := func(r *http.Request, body CreateRequest) (repo.Group, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.svc.Group.CreateGroup(auth, body.Name)
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusCreated)
|
||||
}
|
||||
|
||||
// HandleGroupDelete godoc
|
||||
//
|
||||
// @Summary Delete Group
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 204
|
||||
// @Router /v1/groups/{id} [Delete]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupDelete() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.svc.Group.DeleteGroup(auth)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleGroupInvitationsGetAll godoc
|
||||
//
|
||||
// @Summary Get All Group Invitations
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.GroupInvitation
|
||||
// @Router /v1/groups/invitations [Get]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupInvitationsGetAll() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.GroupInvitation, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Groups.InvitationGetAll(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupMembersGetAll godoc
|
||||
//
|
||||
// @Summary Get All Group Members
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 200 {object} []repo.UserOut
|
||||
// @Router /v1/groups/{id}/members [Get]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupMembersGetAll() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request) ([]repo.UserOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
return ctrl.repo.Users.GetUsersByGroupID(auth, auth.GID)
|
||||
}
|
||||
|
||||
return adapters.Command(fn, http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleGroupMemberAdd godoc
|
||||
//
|
||||
// @Summary Add User to Group
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Param payload body GroupMemberAdd true "User ID"
|
||||
// @Success 204
|
||||
// @Router /v1/groups/{id}/members [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupMemberAdd() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, body GroupMemberAdd) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.svc.Group.AddMember(auth, body.UserID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.Action(fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleGroupMemberRemove godoc
|
||||
//
|
||||
// @Summary Remove User from Group
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Param user_id path string true "User ID"
|
||||
// @Success 204
|
||||
// @Router /v1/groups/{id}/members/{user_id} [Delete]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleGroupMemberRemove() errchain.HandlerFunc {
|
||||
fn := func(r *http.Request, userID uuid.UUID) (any, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
err := ctrl.svc.Group.RemoveMember(auth, userID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adapters.CommandID("user_id", fn, http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -337,9 +337,9 @@ func (ctrl *V1Controller) HandleItemsImport() errchain.HandlerFunc {
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
tenant := services.UseTenantCtx(r.Context())
|
||||
user := services.UseUserCtx(r.Context())
|
||||
|
||||
_, err = ctrl.svc.Items.CsvImport(r.Context(), tenant, file)
|
||||
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, file)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to import items")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HandleBillOfMaterialsExport godoc
|
||||
@@ -17,9 +16,9 @@ import (
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleBillOfMaterialsExport() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
tenant := services.UseTenantCtx(r.Context())
|
||||
actor := services.UseUserCtx(r.Context())
|
||||
|
||||
csv, err := ctrl.svc.Items.ExportBillOfMaterialsCSV(r.Context(), tenant)
|
||||
csv, err := ctrl.svc.Items.ExportBillOfMaterialsCSV(r.Context(), actor.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ import (
|
||||
|
||||
// HandleUserRegistration godoc
|
||||
//
|
||||
// @Summary Register New User
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body services.UserRegistration true "User Data"
|
||||
// @Success 204
|
||||
// @Failure 403 {string} string "Local login is not enabled"
|
||||
// @Router /v1/users/register [Post]
|
||||
// @Summary Register New User
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body services.UserRegistration true "User Data"
|
||||
// @Success 204
|
||||
// @Failure 403 {string} string "Local login is not enabled"
|
||||
// @Router /v1/users/register [Post]
|
||||
func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
// Forbidden if local login is not enabled
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
v1 "github.com/sysadminsmedia/homebox/backend/app/api/handlers/v1"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
|
||||
@@ -153,48 +152,3 @@ func (a *app) mwAuthToken(next errchain.Handler) errchain.Handler {
|
||||
return next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// mwTenant is a middleware that will parse the X-Tenant header and validate the user has access
|
||||
// to the requested tenant. If no header is provided, the user's default group is used.
|
||||
//
|
||||
// WARNING: This middleware _MUST_ be called after mwAuthToken
|
||||
func (a *app) mwTenant(next errchain.Handler) errchain.Handler {
|
||||
return errchain.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := r.Context()
|
||||
|
||||
// Get the user from context (set by mwAuthToken)
|
||||
user := services.UseUserCtx(ctx)
|
||||
if user == nil {
|
||||
return validate.NewRequestError(errors.New("user context not found"), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
tenantID := user.DefaultGroupID
|
||||
|
||||
// Check for X-Tenant header
|
||||
if tenantHeader := r.Header.Get("X-Tenant"); tenantHeader != "" {
|
||||
parsedTenantID, err := uuid.Parse(tenantHeader)
|
||||
if err != nil {
|
||||
return validate.NewRequestError(errors.New("invalid X-Tenant header format"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// Validate user has access to the requested tenant
|
||||
hasAccess := false
|
||||
for _, gid := range user.GroupIDs {
|
||||
if gid == parsedTenantID {
|
||||
hasAccess = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasAccess {
|
||||
return validate.NewRequestError(errors.New("user does not have access to the requested tenant"), http.StatusForbidden)
|
||||
}
|
||||
|
||||
tenantID = parsedTenantID
|
||||
}
|
||||
|
||||
// Set the tenant in context
|
||||
r = r.WithContext(services.SetTenantCtx(ctx, tenantID))
|
||||
return next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -82,13 +82,10 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
|
||||
userMW := []errchain.Middleware{
|
||||
a.mwAuthToken,
|
||||
a.mwTenant,
|
||||
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
|
||||
}
|
||||
|
||||
r.Get("/ws/events", chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
|
||||
|
||||
// User management endpoints
|
||||
r.Get("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
|
||||
r.Put("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
|
||||
r.Delete("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
|
||||
@@ -96,25 +93,16 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
r.Get("/users/refresh", chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
|
||||
r.Put("/users/self/change-password", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
|
||||
|
||||
// Group management endpoints
|
||||
r.Get("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupsGetAll(), userMW...))
|
||||
r.Get("/groups/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
|
||||
r.Post("/groups/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGroupCreate(), userMW...))
|
||||
r.Put("/groups/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
|
||||
r.Delete("/groups/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGroupDelete(), userMW...))
|
||||
|
||||
r.Get("/groups/{id}/members", chain.ToHandlerFunc(v1Ctrl.HandleGroupMembersGetAll(), userMW...))
|
||||
r.Post("/groups/{id}/members", chain.ToHandlerFunc(v1Ctrl.HandleGroupMemberAdd(), userMW...))
|
||||
r.Delete("/groups/{id}/members/{user_id}", chain.ToHandlerFunc(v1Ctrl.HandleGroupMemberRemove(), userMW...))
|
||||
|
||||
r.Get("/groups/invitations", chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsGetAll(), userMW...))
|
||||
r.Post("/groups/invitations", chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
|
||||
r.Get("/groups/statistics", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
|
||||
r.Get("/groups/statistics/purchase-price", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
|
||||
r.Get("/groups/statistics/locations", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
|
||||
r.Get("/groups/statistics/labels", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
|
||||
|
||||
// Action endpoints
|
||||
// TODO: I don't like /groups being the URL for users
|
||||
r.Get("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
|
||||
r.Put("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
|
||||
|
||||
r.Post("/actions/ensure-asset-ids", chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
|
||||
r.Post("/actions/zero-item-time-fields", chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
|
||||
r.Post("/actions/ensure-import-refs", chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
|
||||
@@ -122,7 +110,6 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
r.Post("/actions/create-missing-thumbnails", chain.ToHandlerFunc(v1Ctrl.HandleCreateMissingThumbnails(), userMW...))
|
||||
r.Post("/actions/wipe-inventory", chain.ToHandlerFunc(v1Ctrl.HandleWipeInventory(), userMW...))
|
||||
|
||||
// Location endpoints
|
||||
r.Get("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
|
||||
r.Post("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
|
||||
r.Get("/locations/tree", chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
|
||||
@@ -130,14 +117,12 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
r.Put("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
|
||||
r.Delete("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
|
||||
|
||||
// Labels (tags) endpoints
|
||||
r.Get("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
|
||||
r.Post("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
|
||||
r.Get("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
|
||||
r.Put("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
|
||||
r.Delete("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
|
||||
|
||||
// Item endpoints
|
||||
r.Get("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
|
||||
r.Post("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
|
||||
r.Post("/items/import", chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
|
||||
@@ -152,12 +137,10 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
r.Delete("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
|
||||
r.Post("/items/{id}/duplicate", chain.ToHandlerFunc(v1Ctrl.HandleItemDuplicate(), userMW...))
|
||||
|
||||
// Item attachment endpoints
|
||||
r.Post("/items/{id}/attachments", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
|
||||
r.Put("/items/{id}/attachments/{attachment_id}", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
|
||||
r.Delete("/items/{id}/attachments/{attachment_id}", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
|
||||
|
||||
// Item maintenance endpoints
|
||||
r.Get("/items/{id}/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
|
||||
r.Post("/items/{id}/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
|
||||
|
||||
|
||||
@@ -243,15 +243,12 @@ const docTemplate = `{
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Groups",
|
||||
"summary": "Get Group",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,31 +288,6 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/v1/groups/invitations": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Invitations",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.GroupInvitation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -466,147 +438,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Create Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Group Name",
|
||||
"name": "name",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Delete Group",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Members",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.UserOut"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Add User to Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User ID",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.GroupMemberAdd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members/{user_id}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Remove User from Group",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID",
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/items": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3698,10 +3529,6 @@ const docTemplate = `{
|
||||
"description": "CreatedAt holds the value of the \"created_at\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_group_id": {
|
||||
"description": "DefaultGroupID holds the value of the \"default_group_id\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
|
||||
"allOf": [
|
||||
@@ -3762,12 +3589,13 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/ent.AuthTokens"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups holds the value of the groups edge.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
"group": {
|
||||
"description": "Group holds the value of the group edge.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifiers": {
|
||||
"description": "Notifiers holds the value of the notifiers edge.",
|
||||
@@ -3861,23 +3689,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupStatistics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5095,17 +4906,14 @@ const docTemplate = `{
|
||||
"repo.UserOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultGroupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
@@ -5326,17 +5134,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupMemberAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ItemAttachmentToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -242,17 +242,14 @@
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Groups",
|
||||
"summary": "Get Group",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
}
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,32 +292,6 @@
|
||||
}
|
||||
},
|
||||
"/v1/groups/invitations": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Invitations",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/repo.GroupInvitation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -480,142 +451,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Create Group",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Group Name",
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Delete Group",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Members",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/repo.UserOut"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Add User to Group",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/v1.GroupMemberAdd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "User ID",
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members/{user_id}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Remove User from Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User ID",
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/items": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3892,10 +3727,6 @@
|
||||
"description": "CreatedAt holds the value of the \"created_at\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_group_id": {
|
||||
"description": "DefaultGroupID holds the value of the \"default_group_id\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
|
||||
"allOf": [
|
||||
@@ -3956,12 +3787,13 @@
|
||||
"$ref": "#/components/schemas/ent.AuthTokens"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups holds the value of the groups edge.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ent.Group"
|
||||
}
|
||||
"group": {
|
||||
"description": "Group holds the value of the group edge.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ent.Group"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifiers": {
|
||||
"description": "Notifiers holds the value of the notifiers edge.",
|
||||
@@ -4055,23 +3887,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupStatistics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5289,17 +5104,14 @@
|
||||
"repo.UserOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultGroupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
@@ -5520,17 +5332,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupMemberAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ItemAttachmentToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -142,16 +142,14 @@ paths:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Get All Groups
|
||||
summary: Get Group
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
put:
|
||||
security:
|
||||
- Bearer: []
|
||||
@@ -173,21 +171,6 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
/v1/groups/invitations:
|
||||
get:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Get All Group Invitations
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/repo.GroupInvitation"
|
||||
post:
|
||||
security:
|
||||
- Bearer: []
|
||||
@@ -279,85 +262,6 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/repo.ValueOverTime"
|
||||
"/v1/groups/{id}":
|
||||
post:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Create Group
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: Group Name
|
||||
required: true
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
delete:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Delete Group
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"/v1/groups/{id}/members":
|
||||
get:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Get All Group Members
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/repo.UserOut"
|
||||
post:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Add User to Group
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/v1.GroupMemberAdd"
|
||||
description: User ID
|
||||
required: true
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"/v1/groups/{id}/members/{user_id}":
|
||||
delete:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Remove User from Group
|
||||
parameters:
|
||||
- description: User ID
|
||||
name: user_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
/v1/items:
|
||||
get:
|
||||
security:
|
||||
@@ -2418,9 +2322,6 @@ components:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
default_group_id:
|
||||
description: DefaultGroupID holds the value of the "default_group_id" field.
|
||||
type: string
|
||||
edges:
|
||||
description: >-
|
||||
Edges holds the relations/edges for other nodes in the graph.
|
||||
@@ -2464,11 +2365,10 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ent.AuthTokens"
|
||||
groups:
|
||||
description: Groups holds the value of the groups edge.
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ent.Group"
|
||||
group:
|
||||
description: Group holds the value of the group edge.
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ent.Group"
|
||||
notifiers:
|
||||
description: Notifiers holds the value of the notifiers edge.
|
||||
type: array
|
||||
@@ -2531,17 +2431,6 @@ components:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
repo.GroupInvitation:
|
||||
type: object
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
group:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
id:
|
||||
type: string
|
||||
uses:
|
||||
type: integer
|
||||
repo.GroupStatistics:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3375,14 +3264,12 @@ components:
|
||||
repo.UserOut:
|
||||
type: object
|
||||
properties:
|
||||
defaultGroupId:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
groupIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
groupId:
|
||||
type: string
|
||||
groupName:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isOwner:
|
||||
@@ -3526,13 +3413,6 @@ components:
|
||||
type: integer
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
v1.GroupMemberAdd:
|
||||
type: object
|
||||
required:
|
||||
- userId
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
v1.ItemAttachmentToken:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -241,15 +241,12 @@
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Groups",
|
||||
"summary": "Get Group",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,31 +286,6 @@
|
||||
}
|
||||
},
|
||||
"/v1/groups/invitations": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Invitations",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.GroupInvitation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -464,147 +436,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Create Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Group Name",
|
||||
"name": "name",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Delete Group",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Members",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.UserOut"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Add User to Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User ID",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.GroupMemberAdd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members/{user_id}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Remove User from Group",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID",
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/items": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3696,10 +3527,6 @@
|
||||
"description": "CreatedAt holds the value of the \"created_at\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_group_id": {
|
||||
"description": "DefaultGroupID holds the value of the \"default_group_id\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
|
||||
"allOf": [
|
||||
@@ -3760,12 +3587,13 @@
|
||||
"$ref": "#/definitions/ent.AuthTokens"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups holds the value of the groups edge.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
"group": {
|
||||
"description": "Group holds the value of the group edge.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifiers": {
|
||||
"description": "Notifiers holds the value of the notifiers edge.",
|
||||
@@ -3859,23 +3687,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupStatistics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5093,17 +4904,14 @@
|
||||
"repo.UserOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultGroupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
@@ -5324,17 +5132,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupMemberAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ItemAttachmentToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -719,9 +719,6 @@ definitions:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
default_group_id:
|
||||
description: DefaultGroupID holds the value of the "default_group_id" field.
|
||||
type: string
|
||||
edges:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.UserEdges'
|
||||
@@ -764,11 +761,10 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/ent.AuthTokens'
|
||||
type: array
|
||||
groups:
|
||||
description: Groups holds the value of the groups edge.
|
||||
items:
|
||||
$ref: '#/definitions/ent.Group'
|
||||
type: array
|
||||
group:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.Group'
|
||||
description: Group holds the value of the group edge.
|
||||
notifiers:
|
||||
description: Notifiers holds the value of the notifiers edge.
|
||||
items:
|
||||
@@ -832,17 +828,6 @@ definitions:
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
repo.GroupInvitation:
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
group:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
id:
|
||||
type: string
|
||||
uses:
|
||||
type: integer
|
||||
type: object
|
||||
repo.GroupStatistics:
|
||||
properties:
|
||||
totalItemPrice:
|
||||
@@ -1675,14 +1660,12 @@ definitions:
|
||||
type: object
|
||||
repo.UserOut:
|
||||
properties:
|
||||
defaultGroupId:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
groupIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
groupId:
|
||||
type: string
|
||||
groupName:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isOwner:
|
||||
@@ -1827,13 +1810,6 @@ definitions:
|
||||
required:
|
||||
- uses
|
||||
type: object
|
||||
v1.GroupMemberAdd:
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
required:
|
||||
- userId
|
||||
type: object
|
||||
v1.ItemAttachmentToken:
|
||||
properties:
|
||||
token:
|
||||
@@ -2056,12 +2032,10 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
type: array
|
||||
$ref: '#/definitions/repo.Group'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Groups
|
||||
summary: Get Group
|
||||
tags:
|
||||
- Group
|
||||
put:
|
||||
@@ -2084,106 +2058,7 @@ paths:
|
||||
summary: Update Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}:
|
||||
delete:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Delete Group
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: Group Name
|
||||
in: body
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}/members:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.UserOut'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Group Members
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.GroupMemberAdd'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Add User to Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}/members/{user_id}:
|
||||
delete:
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: user_id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Remove User from Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/invitations:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.GroupInvitation'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Group Invitations
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: User Data
|
||||
|
||||
@@ -325,8 +325,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
@@ -349,8 +347,6 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
|
||||
github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
@@ -393,10 +389,6 @@ github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAX
|
||||
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
||||
@@ -14,7 +14,6 @@ type contextKeys struct {
|
||||
var (
|
||||
ContextUser = &contextKeys{name: "User"}
|
||||
ContextUserToken = &contextKeys{name: "UserToken"}
|
||||
ContextTenant = &contextKeys{name: "Tenant"}
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
@@ -34,14 +33,10 @@ type Context struct {
|
||||
// This extracts the users from the context and embeds it into the ServiceContext struct
|
||||
func NewContext(ctx context.Context) Context {
|
||||
user := UseUserCtx(ctx)
|
||||
gid := UseTenantCtx(ctx)
|
||||
if gid == uuid.Nil && user != nil {
|
||||
gid = user.DefaultGroupID
|
||||
}
|
||||
return Context{
|
||||
Context: ctx,
|
||||
UID: user.ID,
|
||||
GID: gid,
|
||||
GID: user.GroupID,
|
||||
User: user,
|
||||
}
|
||||
}
|
||||
@@ -69,17 +64,3 @@ func UseTokenCtx(ctx context.Context) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UseTenantCtx is a helper function that returns the tenant group ID from the context.
|
||||
// Returns uuid.Nil if not set.
|
||||
func UseTenantCtx(ctx context.Context) uuid.UUID {
|
||||
if val := ctx.Value(ContextTenant); val != nil {
|
||||
return val.(uuid.UUID)
|
||||
}
|
||||
return uuid.Nil
|
||||
}
|
||||
|
||||
// SetTenantCtx is a helper function that sets the ContextTenant in the context.
|
||||
func SetTenantCtx(ctx context.Context, tenantID uuid.UUID) context.Context {
|
||||
return context.WithValue(ctx, ContextTenant, tenantID)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/currencies"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
|
||||
@@ -35,18 +33,18 @@ func bootstrap() {
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
tGroup, err = tRepos.Groups.GroupCreate(ctx, "test-group", uuid.Nil)
|
||||
tGroup, err = tRepos.Groups.GroupCreate(ctx, "test-group")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
password := fk.Str(10)
|
||||
tUser, err = tRepos.Users.Create(ctx, repo.UserCreate{
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: &password,
|
||||
IsSuperuser: fk.Bool(),
|
||||
DefaultGroupID: tGroup.ID,
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: &password,
|
||||
IsSuperuser: fk.Bool(),
|
||||
GroupID: tGroup.ID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"github.com/sysadminsmedia/homebox/backend/pkgs/hasher"
|
||||
)
|
||||
@@ -15,7 +14,7 @@ type GroupService struct {
|
||||
|
||||
func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.Group, error) {
|
||||
if data.Name == "" {
|
||||
return repo.Group{}, errors.New("group name cannot be empty")
|
||||
data.Name = ctx.User.GroupName
|
||||
}
|
||||
|
||||
if data.Currency == "" {
|
||||
@@ -25,18 +24,6 @@ func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.G
|
||||
return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data)
|
||||
}
|
||||
|
||||
func (svc *GroupService) CreateGroup(ctx Context, name string) (repo.Group, error) {
|
||||
if name == "" {
|
||||
return repo.Group{}, errors.New("group name cannot be empty")
|
||||
}
|
||||
|
||||
return svc.repos.Groups.GroupCreate(ctx.Context, name, ctx.UID)
|
||||
}
|
||||
|
||||
func (svc *GroupService) DeleteGroup(ctx Context) error {
|
||||
return svc.repos.Groups.GroupDelete(ctx.Context, ctx.GID)
|
||||
}
|
||||
|
||||
func (svc *GroupService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) {
|
||||
token := hasher.GenerateToken()
|
||||
|
||||
@@ -51,19 +38,3 @@ func (svc *GroupService) NewInvitation(ctx Context, uses int, expiresAt time.Tim
|
||||
|
||||
return token.Raw, nil
|
||||
}
|
||||
|
||||
func (svc *GroupService) AddMember(ctx Context, userID uuid.UUID) error {
|
||||
if userID == uuid.Nil {
|
||||
return errors.New("user ID cannot be empty")
|
||||
}
|
||||
|
||||
return svc.repos.Groups.AddMember(ctx.Context, ctx.GID, userID)
|
||||
}
|
||||
|
||||
func (svc *GroupService) RemoveMember(ctx Context, userID uuid.UUID) error {
|
||||
if userID == uuid.Nil {
|
||||
return errors.New("user ID cannot be empty")
|
||||
}
|
||||
|
||||
return svc.repos.Groups.RemoveMember(ctx.Context, ctx.GID, userID)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
|
||||
case "":
|
||||
log.Debug().Msg("creating new group")
|
||||
creatingGroup = true
|
||||
group, err = svc.repos.Groups.GroupCreate(ctx, "Home", uuid.Nil)
|
||||
group, err = svc.repos.Groups.GroupCreate(ctx, "Home")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create group")
|
||||
return repo.UserOut{}, err
|
||||
@@ -81,12 +81,12 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
|
||||
|
||||
hashed, _ := hasher.HashPassword(data.Password)
|
||||
usrCreate := repo.UserCreate{
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
Password: &hashed,
|
||||
IsSuperuser: false,
|
||||
DefaultGroupID: group.ID,
|
||||
IsOwner: creatingGroup,
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
Password: &hashed,
|
||||
IsSuperuser: false,
|
||||
GroupID: group.ID,
|
||||
IsOwner: creatingGroup,
|
||||
}
|
||||
|
||||
usr, err := svc.repos.Users.Create(ctx, usrCreate)
|
||||
@@ -99,7 +99,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
|
||||
if creatingGroup {
|
||||
log.Debug().Msg("creating default labels")
|
||||
for _, label := range defaultLabels() {
|
||||
_, err := svc.repos.Labels.Create(ctx, usr.DefaultGroupID, label)
|
||||
_, err := svc.repos.Labels.Create(ctx, usr.GroupID, label)
|
||||
if err != nil {
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration)
|
||||
|
||||
log.Debug().Msg("creating default locations")
|
||||
for _, location := range defaultLocations() {
|
||||
_, err := svc.repos.Locations.Create(ctx, usr.DefaultGroupID, location)
|
||||
_, err := svc.repos.Locations.Create(ctx, usr.GroupID, location)
|
||||
if err != nil {
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
@@ -280,19 +280,19 @@ func (svc *UserService) LoginOIDC(ctx context.Context, issuer, subject, email, n
|
||||
|
||||
// registerOIDCUser creates a new user for OIDC authentication with issuer+subject identity.
|
||||
func (svc *UserService) registerOIDCUser(ctx context.Context, issuer, subject, email, name string) (repo.UserOut, error) {
|
||||
group, err := svc.repos.Groups.GroupCreate(ctx, "Home", uuid.Nil)
|
||||
group, err := svc.repos.Groups.GroupCreate(ctx, "Home")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create group for OIDC user")
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
|
||||
usrCreate := repo.UserCreate{
|
||||
Name: name,
|
||||
Email: email,
|
||||
Password: nil,
|
||||
IsSuperuser: false,
|
||||
DefaultGroupID: group.ID,
|
||||
IsOwner: true,
|
||||
Name: name,
|
||||
Email: email,
|
||||
Password: nil,
|
||||
IsSuperuser: false,
|
||||
GroupID: group.ID,
|
||||
IsOwner: true,
|
||||
}
|
||||
|
||||
entUser, err := svc.repos.Users.CreateWithOIDC(ctx, usrCreate, issuer, subject)
|
||||
@@ -369,30 +369,3 @@ func (svc *UserService) ChangePassword(ctx Context, current string, new string)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (svc *UserService) EnsureUserPassword(ctx context.Context, email, password string) error {
|
||||
usr, err := svc.repos.Users.GetOneEmailNoEdges(ctx, email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
match := false
|
||||
if usr.PasswordHash != "" {
|
||||
match, _ = hasher.CheckPasswordHash(password, usr.PasswordHash)
|
||||
}
|
||||
if !match {
|
||||
hash, herr := hasher.HashPassword(password)
|
||||
if herr != nil {
|
||||
return herr
|
||||
}
|
||||
if cerr := svc.repos.Users.ChangePassword(ctx, usr.ID, hash); cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExistsByEmail returns true if a user with the given email exists.
|
||||
func (svc *UserService) ExistsByEmail(ctx context.Context, email string) bool {
|
||||
_, err := svc.repos.Users.GetOneEmailNoEdges(ctx, email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
8
backend/internal/data/ent/client.go
generated
8
backend/internal/data/ent/client.go
generated
@@ -909,7 +909,7 @@ func (c *GroupClient) QueryUsers(_m *Group) *UserQuery {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(group.Table, group.FieldID, id),
|
||||
sqlgraph.To(user.Table, user.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, true, group.UsersTable, group.UsersPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, group.UsersTable, group.UsersColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
@@ -2711,15 +2711,15 @@ func (c *UserClient) GetX(ctx context.Context, id uuid.UUID) *User {
|
||||
return obj
|
||||
}
|
||||
|
||||
// QueryGroups queries the groups edge of a User.
|
||||
func (c *UserClient) QueryGroups(_m *User) *GroupQuery {
|
||||
// QueryGroup queries the group edge of a User.
|
||||
func (c *UserClient) QueryGroup(_m *User) *GroupQuery {
|
||||
query := (&GroupClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(user.Table, user.FieldID, id),
|
||||
sqlgraph.To(group.Table, group.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, false, user.GroupsTable, user.GroupsPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, user.GroupTable, user.GroupColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
|
||||
14
backend/internal/data/ent/group/group.go
generated
14
backend/internal/data/ent/group/group.go
generated
@@ -39,11 +39,13 @@ const (
|
||||
EdgeItemTemplates = "item_templates"
|
||||
// Table holds the table name of the group in the database.
|
||||
Table = "groups"
|
||||
// UsersTable is the table that holds the users relation/edge. The primary key declared below.
|
||||
UsersTable = "user_groups"
|
||||
// UsersTable is the table that holds the users relation/edge.
|
||||
UsersTable = "users"
|
||||
// UsersInverseTable is the table name for the User entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "user" package.
|
||||
UsersInverseTable = "users"
|
||||
// UsersColumn is the table column denoting the users relation/edge.
|
||||
UsersColumn = "group_users"
|
||||
// LocationsTable is the table that holds the locations relation/edge.
|
||||
LocationsTable = "locations"
|
||||
// LocationsInverseTable is the table name for the Location entity.
|
||||
@@ -97,12 +99,6 @@ var Columns = []string{
|
||||
FieldCurrency,
|
||||
}
|
||||
|
||||
var (
|
||||
// UsersPrimaryKey and UsersColumn2 are the table columns denoting the
|
||||
// primary key for the users relation (M2M).
|
||||
UsersPrimaryKey = []string{"user_id", "group_id"}
|
||||
)
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
@@ -257,7 +253,7 @@ func newUsersStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(UsersInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, true, UsersTable, UsersPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, UsersTable, UsersColumn),
|
||||
)
|
||||
}
|
||||
func newLocationsStep() *sqlgraph.Step {
|
||||
|
||||
2
backend/internal/data/ent/group/where.go
generated
2
backend/internal/data/ent/group/where.go
generated
@@ -291,7 +291,7 @@ func HasUsers() predicate.Group {
|
||||
return predicate.Group(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, true, UsersTable, UsersPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, UsersTable, UsersColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
|
||||
6
backend/internal/data/ent/group_create.go
generated
6
backend/internal/data/ent/group_create.go
generated
@@ -320,10 +320,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
||||
}
|
||||
if nodes := _c.mutation.UsersIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
|
||||
68
backend/internal/data/ent/group_query.go
generated
68
backend/internal/data/ent/group_query.go
generated
@@ -88,7 +88,7 @@ func (_q *GroupQuery) QueryUsers() *UserQuery {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(group.Table, group.FieldID, selector),
|
||||
sqlgraph.To(user.Table, user.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, true, group.UsersTable, group.UsersPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, group.UsersTable, group.UsersColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
@@ -671,63 +671,33 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
|
||||
}
|
||||
|
||||
func (_q *GroupQuery) loadUsers(ctx context.Context, query *UserQuery, nodes []*Group, init func(*Group), assign func(*Group, *User)) error {
|
||||
edgeIDs := make([]driver.Value, len(nodes))
|
||||
byID := make(map[uuid.UUID]*Group)
|
||||
nids := make(map[uuid.UUID]map[*Group]struct{})
|
||||
for i, node := range nodes {
|
||||
edgeIDs[i] = node.ID
|
||||
byID[node.ID] = node
|
||||
fks := make([]driver.Value, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID]*Group)
|
||||
for i := range nodes {
|
||||
fks = append(fks, nodes[i].ID)
|
||||
nodeids[nodes[i].ID] = nodes[i]
|
||||
if init != nil {
|
||||
init(node)
|
||||
init(nodes[i])
|
||||
}
|
||||
}
|
||||
query.Where(func(s *sql.Selector) {
|
||||
joinT := sql.Table(group.UsersTable)
|
||||
s.Join(joinT).On(s.C(user.FieldID), joinT.C(group.UsersPrimaryKey[0]))
|
||||
s.Where(sql.InValues(joinT.C(group.UsersPrimaryKey[1]), edgeIDs...))
|
||||
columns := s.SelectedColumns()
|
||||
s.Select(joinT.C(group.UsersPrimaryKey[1]))
|
||||
s.AppendSelect(columns...)
|
||||
s.SetDistinct(false)
|
||||
})
|
||||
if err := query.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
|
||||
return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) {
|
||||
assign := spec.Assign
|
||||
values := spec.ScanValues
|
||||
spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
values, err := values(columns[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]any{new(uuid.UUID)}, values...), nil
|
||||
}
|
||||
spec.Assign = func(columns []string, values []any) error {
|
||||
outValue := *values[0].(*uuid.UUID)
|
||||
inValue := *values[1].(*uuid.UUID)
|
||||
if nids[inValue] == nil {
|
||||
nids[inValue] = map[*Group]struct{}{byID[outValue]: {}}
|
||||
return assign(columns[1:], values[1:])
|
||||
}
|
||||
nids[inValue][byID[outValue]] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
})
|
||||
neighbors, err := withInterceptors[[]*User](ctx, query, qr, query.inters)
|
||||
query.withFKs = true
|
||||
query.Where(predicate.User(func(s *sql.Selector) {
|
||||
s.Where(sql.InValues(s.C(group.UsersColumn), fks...))
|
||||
}))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
nodes, ok := nids[n.ID]
|
||||
fk := n.group_users
|
||||
if fk == nil {
|
||||
return fmt.Errorf(`foreign-key "group_users" is nil for node %v`, n.ID)
|
||||
}
|
||||
node, ok := nodeids[*fk]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected "users" node returned %v`, n.ID)
|
||||
}
|
||||
for kn := range nodes {
|
||||
assign(kn, n)
|
||||
return fmt.Errorf(`unexpected referenced foreign-key "group_users" returned %v for node %v`, *fk, n.ID)
|
||||
}
|
||||
assign(node, n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
36
backend/internal/data/ent/group_update.go
generated
36
backend/internal/data/ent/group_update.go
generated
@@ -396,10 +396,10 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
}
|
||||
if _u.mutation.UsersCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
@@ -409,10 +409,10 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
}
|
||||
if nodes := _u.mutation.RemovedUsersIDs(); len(nodes) > 0 && !_u.mutation.UsersCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
@@ -425,10 +425,10 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
}
|
||||
if nodes := _u.mutation.UsersIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
@@ -1119,10 +1119,10 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
}
|
||||
if _u.mutation.UsersCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
@@ -1132,10 +1132,10 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedUsersIDs(); len(nodes) > 0 && !_u.mutation.UsersCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
@@ -1148,10 +1148,10 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
}
|
||||
if nodes := _u.mutation.UsersIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: true,
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: group.UsersTable,
|
||||
Columns: group.UsersPrimaryKey,
|
||||
Columns: []string{group.UsersColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID),
|
||||
|
||||
39
backend/internal/data/ent/migrate/schema.go
generated
39
backend/internal/data/ent/migrate/schema.go
generated
@@ -468,13 +468,21 @@ var (
|
||||
{Name: "activated_on", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "oidc_issuer", Type: field.TypeString, Nullable: true},
|
||||
{Name: "oidc_subject", Type: field.TypeString, Nullable: true},
|
||||
{Name: "default_group_id", Type: field.TypeUUID, Nullable: true},
|
||||
{Name: "group_users", Type: field.TypeUUID},
|
||||
}
|
||||
// UsersTable holds the schema information for the "users" table.
|
||||
UsersTable = &schema.Table{
|
||||
Name: "users",
|
||||
Columns: UsersColumns,
|
||||
PrimaryKey: []*schema.Column{UsersColumns[0]},
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "users_groups_users",
|
||||
Columns: []*schema.Column{UsersColumns[12]},
|
||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "user_oidc_issuer_oidc_subject",
|
||||
@@ -508,31 +516,6 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
// UserGroupsColumns holds the columns for the "user_groups" table.
|
||||
UserGroupsColumns = []*schema.Column{
|
||||
{Name: "user_id", Type: field.TypeUUID},
|
||||
{Name: "group_id", Type: field.TypeUUID},
|
||||
}
|
||||
// UserGroupsTable holds the schema information for the "user_groups" table.
|
||||
UserGroupsTable = &schema.Table{
|
||||
Name: "user_groups",
|
||||
Columns: UserGroupsColumns,
|
||||
PrimaryKey: []*schema.Column{UserGroupsColumns[0], UserGroupsColumns[1]},
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "user_groups_user_id",
|
||||
Columns: []*schema.Column{UserGroupsColumns[0]},
|
||||
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
{
|
||||
Symbol: "user_groups_group_id",
|
||||
Columns: []*schema.Column{UserGroupsColumns[1]},
|
||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Tables holds all the tables in the schema.
|
||||
Tables = []*schema.Table{
|
||||
AttachmentsTable,
|
||||
@@ -550,7 +533,6 @@ var (
|
||||
TemplateFieldsTable,
|
||||
UsersTable,
|
||||
LabelItemsTable,
|
||||
UserGroupsTable,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -573,8 +555,7 @@ func init() {
|
||||
NotifiersTable.ForeignKeys[0].RefTable = GroupsTable
|
||||
NotifiersTable.ForeignKeys[1].RefTable = UsersTable
|
||||
TemplateFieldsTable.ForeignKeys[0].RefTable = ItemTemplatesTable
|
||||
UsersTable.ForeignKeys[0].RefTable = GroupsTable
|
||||
LabelItemsTable.ForeignKeys[0].RefTable = LabelsTable
|
||||
LabelItemsTable.ForeignKeys[1].RefTable = ItemsTable
|
||||
UserGroupsTable.ForeignKeys[0].RefTable = UsersTable
|
||||
UserGroupsTable.ForeignKeys[1].RefTable = GroupsTable
|
||||
}
|
||||
|
||||
177
backend/internal/data/ent/mutation.go
generated
177
backend/internal/data/ent/mutation.go
generated
@@ -12583,11 +12583,9 @@ type UserMutation struct {
|
||||
activated_on *time.Time
|
||||
oidc_issuer *string
|
||||
oidc_subject *string
|
||||
default_group_id *uuid.UUID
|
||||
clearedFields map[string]struct{}
|
||||
groups map[uuid.UUID]struct{}
|
||||
removedgroups map[uuid.UUID]struct{}
|
||||
clearedgroups bool
|
||||
group *uuid.UUID
|
||||
clearedgroup bool
|
||||
auth_tokens map[uuid.UUID]struct{}
|
||||
removedauth_tokens map[uuid.UUID]struct{}
|
||||
clearedauth_tokens bool
|
||||
@@ -13151,107 +13149,43 @@ func (m *UserMutation) ResetOidcSubject() {
|
||||
delete(m.clearedFields, user.FieldOidcSubject)
|
||||
}
|
||||
|
||||
// SetDefaultGroupID sets the "default_group_id" field.
|
||||
func (m *UserMutation) SetDefaultGroupID(u uuid.UUID) {
|
||||
m.default_group_id = &u
|
||||
// SetGroupID sets the "group" edge to the Group entity by id.
|
||||
func (m *UserMutation) SetGroupID(id uuid.UUID) {
|
||||
m.group = &id
|
||||
}
|
||||
|
||||
// DefaultGroupID returns the value of the "default_group_id" field in the mutation.
|
||||
func (m *UserMutation) DefaultGroupID() (r uuid.UUID, exists bool) {
|
||||
v := m.default_group_id
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
// ClearGroup clears the "group" edge to the Group entity.
|
||||
func (m *UserMutation) ClearGroup() {
|
||||
m.clearedgroup = true
|
||||
}
|
||||
|
||||
// OldDefaultGroupID returns the old "default_group_id" field's value of the User entity.
|
||||
// If the User object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *UserMutation) OldDefaultGroupID(ctx context.Context) (v *uuid.UUID, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldDefaultGroupID is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldDefaultGroupID requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldDefaultGroupID: %w", err)
|
||||
}
|
||||
return oldValue.DefaultGroupID, nil
|
||||
// GroupCleared reports if the "group" edge to the Group entity was cleared.
|
||||
func (m *UserMutation) GroupCleared() bool {
|
||||
return m.clearedgroup
|
||||
}
|
||||
|
||||
// ClearDefaultGroupID clears the value of the "default_group_id" field.
|
||||
func (m *UserMutation) ClearDefaultGroupID() {
|
||||
m.default_group_id = nil
|
||||
m.clearedFields[user.FieldDefaultGroupID] = struct{}{}
|
||||
}
|
||||
|
||||
// DefaultGroupIDCleared returns if the "default_group_id" field was cleared in this mutation.
|
||||
func (m *UserMutation) DefaultGroupIDCleared() bool {
|
||||
_, ok := m.clearedFields[user.FieldDefaultGroupID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetDefaultGroupID resets all changes to the "default_group_id" field.
|
||||
func (m *UserMutation) ResetDefaultGroupID() {
|
||||
m.default_group_id = nil
|
||||
delete(m.clearedFields, user.FieldDefaultGroupID)
|
||||
}
|
||||
|
||||
// AddGroupIDs adds the "groups" edge to the Group entity by ids.
|
||||
func (m *UserMutation) AddGroupIDs(ids ...uuid.UUID) {
|
||||
if m.groups == nil {
|
||||
m.groups = make(map[uuid.UUID]struct{})
|
||||
}
|
||||
for i := range ids {
|
||||
m.groups[ids[i]] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// ClearGroups clears the "groups" edge to the Group entity.
|
||||
func (m *UserMutation) ClearGroups() {
|
||||
m.clearedgroups = true
|
||||
}
|
||||
|
||||
// GroupsCleared reports if the "groups" edge to the Group entity was cleared.
|
||||
func (m *UserMutation) GroupsCleared() bool {
|
||||
return m.clearedgroups
|
||||
}
|
||||
|
||||
// RemoveGroupIDs removes the "groups" edge to the Group entity by IDs.
|
||||
func (m *UserMutation) RemoveGroupIDs(ids ...uuid.UUID) {
|
||||
if m.removedgroups == nil {
|
||||
m.removedgroups = make(map[uuid.UUID]struct{})
|
||||
}
|
||||
for i := range ids {
|
||||
delete(m.groups, ids[i])
|
||||
m.removedgroups[ids[i]] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// RemovedGroups returns the removed IDs of the "groups" edge to the Group entity.
|
||||
func (m *UserMutation) RemovedGroupsIDs() (ids []uuid.UUID) {
|
||||
for id := range m.removedgroups {
|
||||
ids = append(ids, id)
|
||||
// GroupID returns the "group" edge ID in the mutation.
|
||||
func (m *UserMutation) GroupID() (id uuid.UUID, exists bool) {
|
||||
if m.group != nil {
|
||||
return *m.group, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GroupsIDs returns the "groups" edge IDs in the mutation.
|
||||
func (m *UserMutation) GroupsIDs() (ids []uuid.UUID) {
|
||||
for id := range m.groups {
|
||||
ids = append(ids, id)
|
||||
// GroupIDs returns the "group" edge IDs in the mutation.
|
||||
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
|
||||
// GroupID instead. It exists only for internal usage by the builders.
|
||||
func (m *UserMutation) GroupIDs() (ids []uuid.UUID) {
|
||||
if id := m.group; id != nil {
|
||||
ids = append(ids, *id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResetGroups resets all changes to the "groups" edge.
|
||||
func (m *UserMutation) ResetGroups() {
|
||||
m.groups = nil
|
||||
m.clearedgroups = false
|
||||
m.removedgroups = nil
|
||||
// ResetGroup resets all changes to the "group" edge.
|
||||
func (m *UserMutation) ResetGroup() {
|
||||
m.group = nil
|
||||
m.clearedgroup = false
|
||||
}
|
||||
|
||||
// AddAuthTokenIDs adds the "auth_tokens" edge to the AuthTokens entity by ids.
|
||||
@@ -13396,7 +13330,7 @@ func (m *UserMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *UserMutation) Fields() []string {
|
||||
fields := make([]string, 0, 12)
|
||||
fields := make([]string, 0, 11)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, user.FieldCreatedAt)
|
||||
}
|
||||
@@ -13430,9 +13364,6 @@ func (m *UserMutation) Fields() []string {
|
||||
if m.oidc_subject != nil {
|
||||
fields = append(fields, user.FieldOidcSubject)
|
||||
}
|
||||
if m.default_group_id != nil {
|
||||
fields = append(fields, user.FieldDefaultGroupID)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -13463,8 +13394,6 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.OidcIssuer()
|
||||
case user.FieldOidcSubject:
|
||||
return m.OidcSubject()
|
||||
case user.FieldDefaultGroupID:
|
||||
return m.DefaultGroupID()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -13496,8 +13425,6 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er
|
||||
return m.OldOidcIssuer(ctx)
|
||||
case user.FieldOidcSubject:
|
||||
return m.OldOidcSubject(ctx)
|
||||
case user.FieldDefaultGroupID:
|
||||
return m.OldDefaultGroupID(ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown User field %s", name)
|
||||
}
|
||||
@@ -13584,13 +13511,6 @@ func (m *UserMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetOidcSubject(v)
|
||||
return nil
|
||||
case user.FieldDefaultGroupID:
|
||||
v, ok := value.(uuid.UUID)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetDefaultGroupID(v)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown User field %s", name)
|
||||
}
|
||||
@@ -13633,9 +13553,6 @@ func (m *UserMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(user.FieldOidcSubject) {
|
||||
fields = append(fields, user.FieldOidcSubject)
|
||||
}
|
||||
if m.FieldCleared(user.FieldDefaultGroupID) {
|
||||
fields = append(fields, user.FieldDefaultGroupID)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -13662,9 +13579,6 @@ func (m *UserMutation) ClearField(name string) error {
|
||||
case user.FieldOidcSubject:
|
||||
m.ClearOidcSubject()
|
||||
return nil
|
||||
case user.FieldDefaultGroupID:
|
||||
m.ClearDefaultGroupID()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown User nullable field %s", name)
|
||||
}
|
||||
@@ -13706,9 +13620,6 @@ func (m *UserMutation) ResetField(name string) error {
|
||||
case user.FieldOidcSubject:
|
||||
m.ResetOidcSubject()
|
||||
return nil
|
||||
case user.FieldDefaultGroupID:
|
||||
m.ResetDefaultGroupID()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown User field %s", name)
|
||||
}
|
||||
@@ -13716,8 +13627,8 @@ func (m *UserMutation) ResetField(name string) error {
|
||||
// AddedEdges returns all edge names that were set/added in this mutation.
|
||||
func (m *UserMutation) AddedEdges() []string {
|
||||
edges := make([]string, 0, 3)
|
||||
if m.groups != nil {
|
||||
edges = append(edges, user.EdgeGroups)
|
||||
if m.group != nil {
|
||||
edges = append(edges, user.EdgeGroup)
|
||||
}
|
||||
if m.auth_tokens != nil {
|
||||
edges = append(edges, user.EdgeAuthTokens)
|
||||
@@ -13732,12 +13643,10 @@ func (m *UserMutation) AddedEdges() []string {
|
||||
// name in this mutation.
|
||||
func (m *UserMutation) AddedIDs(name string) []ent.Value {
|
||||
switch name {
|
||||
case user.EdgeGroups:
|
||||
ids := make([]ent.Value, 0, len(m.groups))
|
||||
for id := range m.groups {
|
||||
ids = append(ids, id)
|
||||
case user.EdgeGroup:
|
||||
if id := m.group; id != nil {
|
||||
return []ent.Value{*id}
|
||||
}
|
||||
return ids
|
||||
case user.EdgeAuthTokens:
|
||||
ids := make([]ent.Value, 0, len(m.auth_tokens))
|
||||
for id := range m.auth_tokens {
|
||||
@@ -13757,9 +13666,6 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value {
|
||||
// RemovedEdges returns all edge names that were removed in this mutation.
|
||||
func (m *UserMutation) RemovedEdges() []string {
|
||||
edges := make([]string, 0, 3)
|
||||
if m.removedgroups != nil {
|
||||
edges = append(edges, user.EdgeGroups)
|
||||
}
|
||||
if m.removedauth_tokens != nil {
|
||||
edges = append(edges, user.EdgeAuthTokens)
|
||||
}
|
||||
@@ -13773,12 +13679,6 @@ func (m *UserMutation) RemovedEdges() []string {
|
||||
// the given name in this mutation.
|
||||
func (m *UserMutation) RemovedIDs(name string) []ent.Value {
|
||||
switch name {
|
||||
case user.EdgeGroups:
|
||||
ids := make([]ent.Value, 0, len(m.removedgroups))
|
||||
for id := range m.removedgroups {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids
|
||||
case user.EdgeAuthTokens:
|
||||
ids := make([]ent.Value, 0, len(m.removedauth_tokens))
|
||||
for id := range m.removedauth_tokens {
|
||||
@@ -13798,8 +13698,8 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value {
|
||||
// ClearedEdges returns all edge names that were cleared in this mutation.
|
||||
func (m *UserMutation) ClearedEdges() []string {
|
||||
edges := make([]string, 0, 3)
|
||||
if m.clearedgroups {
|
||||
edges = append(edges, user.EdgeGroups)
|
||||
if m.clearedgroup {
|
||||
edges = append(edges, user.EdgeGroup)
|
||||
}
|
||||
if m.clearedauth_tokens {
|
||||
edges = append(edges, user.EdgeAuthTokens)
|
||||
@@ -13814,8 +13714,8 @@ func (m *UserMutation) ClearedEdges() []string {
|
||||
// was cleared in this mutation.
|
||||
func (m *UserMutation) EdgeCleared(name string) bool {
|
||||
switch name {
|
||||
case user.EdgeGroups:
|
||||
return m.clearedgroups
|
||||
case user.EdgeGroup:
|
||||
return m.clearedgroup
|
||||
case user.EdgeAuthTokens:
|
||||
return m.clearedauth_tokens
|
||||
case user.EdgeNotifiers:
|
||||
@@ -13828,6 +13728,9 @@ func (m *UserMutation) EdgeCleared(name string) bool {
|
||||
// if that edge is not defined in the schema.
|
||||
func (m *UserMutation) ClearEdge(name string) error {
|
||||
switch name {
|
||||
case user.EdgeGroup:
|
||||
m.ClearGroup()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown User unique edge %s", name)
|
||||
}
|
||||
@@ -13836,8 +13739,8 @@ func (m *UserMutation) ClearEdge(name string) error {
|
||||
// It returns an error if the edge is not defined in the schema.
|
||||
func (m *UserMutation) ResetEdge(name string) error {
|
||||
switch name {
|
||||
case user.EdgeGroups:
|
||||
m.ResetGroups()
|
||||
case user.EdgeGroup:
|
||||
m.ResetGroup()
|
||||
return nil
|
||||
case user.EdgeAuthTokens:
|
||||
m.ResetAuthTokens()
|
||||
|
||||
@@ -42,8 +42,7 @@ func (Group) Edges() []ent.Edge {
|
||||
}
|
||||
|
||||
return []ent.Edge{
|
||||
// Use edge.From + Ref("groups") to model M:M between users and groups via junction table
|
||||
edge.From("users", User.Type).Ref("groups"),
|
||||
owned("users", User.Type),
|
||||
owned("locations", Location.Type),
|
||||
owned("items", Item.Type),
|
||||
owned("labels", Label.Type),
|
||||
@@ -73,14 +72,14 @@ func (g GroupMixin) Fields() []ent.Field {
|
||||
}
|
||||
|
||||
func (g GroupMixin) Edges() []ent.Edge {
|
||||
e := edge.From("group", Group.Type).
|
||||
edge := edge.From("group", Group.Type).
|
||||
Ref(g.ref).
|
||||
Unique().
|
||||
Required()
|
||||
|
||||
if g.field != "" {
|
||||
e = e.Field(g.field)
|
||||
edge = edge.Field(g.field)
|
||||
}
|
||||
|
||||
return []ent.Edge{e}
|
||||
return []ent.Edge{edge}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type User struct {
|
||||
func (User) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.BaseMixin{},
|
||||
GroupMixin{ref: "users"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +54,6 @@ func (User) Fields() []ent.Field {
|
||||
field.String("oidc_subject").
|
||||
Optional().
|
||||
Nillable(),
|
||||
// default_group_id is the user's primary tenant/group
|
||||
field.UUID("default_group_id", uuid.UUID{}).
|
||||
Optional().
|
||||
Nillable(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +66,6 @@ func (User) Indexes() []ent.Index {
|
||||
// Edges of the User.
|
||||
func (User) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("groups", Group.Type),
|
||||
edge.To("auth_tokens", AuthTokens.Type).
|
||||
Annotations(entsql.Annotation{
|
||||
OnDelete: entsql.Cascade,
|
||||
|
||||
45
backend/internal/data/ent/user.go
generated
45
backend/internal/data/ent/user.go
generated
@@ -10,6 +10,7 @@ import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
|
||||
)
|
||||
|
||||
@@ -40,18 +41,17 @@ type User struct {
|
||||
OidcIssuer *string `json:"oidc_issuer,omitempty"`
|
||||
// OidcSubject holds the value of the "oidc_subject" field.
|
||||
OidcSubject *string `json:"oidc_subject,omitempty"`
|
||||
// DefaultGroupID holds the value of the "default_group_id" field.
|
||||
DefaultGroupID *uuid.UUID `json:"default_group_id,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the UserQuery when eager-loading is set.
|
||||
Edges UserEdges `json:"edges"`
|
||||
group_users *uuid.UUID
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// UserEdges holds the relations/edges for other nodes in the graph.
|
||||
type UserEdges struct {
|
||||
// Groups holds the value of the groups edge.
|
||||
Groups []*Group `json:"groups,omitempty"`
|
||||
// Group holds the value of the group edge.
|
||||
Group *Group `json:"group,omitempty"`
|
||||
// AuthTokens holds the value of the auth_tokens edge.
|
||||
AuthTokens []*AuthTokens `json:"auth_tokens,omitempty"`
|
||||
// Notifiers holds the value of the notifiers edge.
|
||||
@@ -61,13 +61,15 @@ type UserEdges struct {
|
||||
loadedTypes [3]bool
|
||||
}
|
||||
|
||||
// GroupsOrErr returns the Groups value or an error if the edge
|
||||
// was not loaded in eager-loading.
|
||||
func (e UserEdges) GroupsOrErr() ([]*Group, error) {
|
||||
if e.loadedTypes[0] {
|
||||
return e.Groups, nil
|
||||
// GroupOrErr returns the Group value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e UserEdges) GroupOrErr() (*Group, error) {
|
||||
if e.Group != nil {
|
||||
return e.Group, nil
|
||||
} else if e.loadedTypes[0] {
|
||||
return nil, &NotFoundError{label: group.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "groups"}
|
||||
return nil, &NotLoadedError{edge: "group"}
|
||||
}
|
||||
|
||||
// AuthTokensOrErr returns the AuthTokens value or an error if the edge
|
||||
@@ -93,8 +95,6 @@ func (*User) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case user.FieldDefaultGroupID:
|
||||
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
|
||||
case user.FieldIsSuperuser, user.FieldSuperuser:
|
||||
values[i] = new(sql.NullBool)
|
||||
case user.FieldName, user.FieldEmail, user.FieldPassword, user.FieldRole, user.FieldOidcIssuer, user.FieldOidcSubject:
|
||||
@@ -103,6 +103,8 @@ func (*User) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullTime)
|
||||
case user.FieldID:
|
||||
values[i] = new(uuid.UUID)
|
||||
case user.ForeignKeys[0]: // group_users
|
||||
values[i] = &sql.NullScanner{S: new(uuid.UUID)}
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
@@ -193,12 +195,12 @@ func (_m *User) assignValues(columns []string, values []any) error {
|
||||
_m.OidcSubject = new(string)
|
||||
*_m.OidcSubject = value.String
|
||||
}
|
||||
case user.FieldDefaultGroupID:
|
||||
case user.ForeignKeys[0]:
|
||||
if value, ok := values[i].(*sql.NullScanner); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field default_group_id", values[i])
|
||||
return fmt.Errorf("unexpected type %T for field group_users", values[i])
|
||||
} else if value.Valid {
|
||||
_m.DefaultGroupID = new(uuid.UUID)
|
||||
*_m.DefaultGroupID = *value.S.(*uuid.UUID)
|
||||
_m.group_users = new(uuid.UUID)
|
||||
*_m.group_users = *value.S.(*uuid.UUID)
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
@@ -213,9 +215,9 @@ func (_m *User) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// QueryGroups queries the "groups" edge of the User entity.
|
||||
func (_m *User) QueryGroups() *GroupQuery {
|
||||
return NewUserClient(_m.config).QueryGroups(_m)
|
||||
// QueryGroup queries the "group" edge of the User entity.
|
||||
func (_m *User) QueryGroup() *GroupQuery {
|
||||
return NewUserClient(_m.config).QueryGroup(_m)
|
||||
}
|
||||
|
||||
// QueryAuthTokens queries the "auth_tokens" edge of the User entity.
|
||||
@@ -286,11 +288,6 @@ func (_m *User) String() string {
|
||||
builder.WriteString("oidc_subject=")
|
||||
builder.WriteString(*v)
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.DefaultGroupID; v != nil {
|
||||
builder.WriteString("default_group_id=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
56
backend/internal/data/ent/user/user.go
generated
56
backend/internal/data/ent/user/user.go
generated
@@ -38,21 +38,21 @@ const (
|
||||
FieldOidcIssuer = "oidc_issuer"
|
||||
// FieldOidcSubject holds the string denoting the oidc_subject field in the database.
|
||||
FieldOidcSubject = "oidc_subject"
|
||||
// FieldDefaultGroupID holds the string denoting the default_group_id field in the database.
|
||||
FieldDefaultGroupID = "default_group_id"
|
||||
// EdgeGroups holds the string denoting the groups edge name in mutations.
|
||||
EdgeGroups = "groups"
|
||||
// EdgeGroup holds the string denoting the group edge name in mutations.
|
||||
EdgeGroup = "group"
|
||||
// EdgeAuthTokens holds the string denoting the auth_tokens edge name in mutations.
|
||||
EdgeAuthTokens = "auth_tokens"
|
||||
// EdgeNotifiers holds the string denoting the notifiers edge name in mutations.
|
||||
EdgeNotifiers = "notifiers"
|
||||
// Table holds the table name of the user in the database.
|
||||
Table = "users"
|
||||
// GroupsTable is the table that holds the groups relation/edge. The primary key declared below.
|
||||
GroupsTable = "user_groups"
|
||||
// GroupsInverseTable is the table name for the Group entity.
|
||||
// GroupTable is the table that holds the group relation/edge.
|
||||
GroupTable = "users"
|
||||
// GroupInverseTable is the table name for the Group entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "group" package.
|
||||
GroupsInverseTable = "groups"
|
||||
GroupInverseTable = "groups"
|
||||
// GroupColumn is the table column denoting the group relation/edge.
|
||||
GroupColumn = "group_users"
|
||||
// AuthTokensTable is the table that holds the auth_tokens relation/edge.
|
||||
AuthTokensTable = "auth_tokens"
|
||||
// AuthTokensInverseTable is the table name for the AuthTokens entity.
|
||||
@@ -83,14 +83,13 @@ var Columns = []string{
|
||||
FieldActivatedOn,
|
||||
FieldOidcIssuer,
|
||||
FieldOidcSubject,
|
||||
FieldDefaultGroupID,
|
||||
}
|
||||
|
||||
var (
|
||||
// GroupsPrimaryKey and GroupsColumn2 are the table columns denoting the
|
||||
// primary key for the groups relation (M2M).
|
||||
GroupsPrimaryKey = []string{"user_id", "group_id"}
|
||||
)
|
||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "users"
|
||||
// table and are not defined as standalone fields in the schema.
|
||||
var ForeignKeys = []string{
|
||||
"group_users",
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
@@ -99,6 +98,11 @@ func ValidColumn(column string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range ForeignKeys {
|
||||
if column == ForeignKeys[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -212,22 +216,10 @@ func ByOidcSubject(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldOidcSubject, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByDefaultGroupID orders the results by the default_group_id field.
|
||||
func ByDefaultGroupID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldDefaultGroupID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByGroupsCount orders the results by groups count.
|
||||
func ByGroupsCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
// ByGroupField orders the results by group field.
|
||||
func ByGroupField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborsCount(s, newGroupsStep(), opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByGroups orders the results by groups terms.
|
||||
func ByGroups(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newGroupsStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
sqlgraph.OrderByNeighborTerms(s, newGroupStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,11 +250,11 @@ func ByNotifiers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
sqlgraph.OrderByNeighborTerms(s, newNotifiersStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
}
|
||||
}
|
||||
func newGroupsStep() *sqlgraph.Step {
|
||||
func newGroupStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(GroupsInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, false, GroupsTable, GroupsPrimaryKey...),
|
||||
sqlgraph.To(GroupInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
|
||||
)
|
||||
}
|
||||
func newAuthTokensStep() *sqlgraph.Step {
|
||||
|
||||
67
backend/internal/data/ent/user/where.go
generated
67
backend/internal/data/ent/user/where.go
generated
@@ -106,11 +106,6 @@ func OidcSubject(v string) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldOidcSubject, v))
|
||||
}
|
||||
|
||||
// DefaultGroupID applies equality check predicate on the "default_group_id" field. It's identical to DefaultGroupIDEQ.
|
||||
func DefaultGroupID(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldCreatedAt, v))
|
||||
@@ -636,71 +631,21 @@ func OidcSubjectContainsFold(v string) predicate.User {
|
||||
return predicate.User(sql.FieldContainsFold(FieldOidcSubject, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDEQ applies the EQ predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDEQ(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDNEQ applies the NEQ predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDNEQ(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldNEQ(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDIn applies the In predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDIn(vs ...uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldIn(FieldDefaultGroupID, vs...))
|
||||
}
|
||||
|
||||
// DefaultGroupIDNotIn applies the NotIn predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDNotIn(vs ...uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldNotIn(FieldDefaultGroupID, vs...))
|
||||
}
|
||||
|
||||
// DefaultGroupIDGT applies the GT predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDGT(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldGT(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDGTE applies the GTE predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDGTE(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldGTE(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDLT applies the LT predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDLT(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldLT(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDLTE applies the LTE predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDLTE(v uuid.UUID) predicate.User {
|
||||
return predicate.User(sql.FieldLTE(FieldDefaultGroupID, v))
|
||||
}
|
||||
|
||||
// DefaultGroupIDIsNil applies the IsNil predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDIsNil() predicate.User {
|
||||
return predicate.User(sql.FieldIsNull(FieldDefaultGroupID))
|
||||
}
|
||||
|
||||
// DefaultGroupIDNotNil applies the NotNil predicate on the "default_group_id" field.
|
||||
func DefaultGroupIDNotNil() predicate.User {
|
||||
return predicate.User(sql.FieldNotNull(FieldDefaultGroupID))
|
||||
}
|
||||
|
||||
// HasGroups applies the HasEdge predicate on the "groups" edge.
|
||||
func HasGroups() predicate.User {
|
||||
// HasGroup applies the HasEdge predicate on the "group" edge.
|
||||
func HasGroup() predicate.User {
|
||||
return predicate.User(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, false, GroupsTable, GroupsPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, GroupTable, GroupColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasGroupsWith applies the HasEdge predicate on the "groups" edge with a given conditions (other predicates).
|
||||
func HasGroupsWith(preds ...predicate.Group) predicate.User {
|
||||
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
|
||||
func HasGroupWith(preds ...predicate.Group) predicate.User {
|
||||
return predicate.User(func(s *sql.Selector) {
|
||||
step := newGroupsStep()
|
||||
step := newGroupStep()
|
||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||
for _, p := range preds {
|
||||
p(s)
|
||||
|
||||
48
backend/internal/data/ent/user_create.go
generated
48
backend/internal/data/ent/user_create.go
generated
@@ -162,20 +162,6 @@ func (_c *UserCreate) SetNillableOidcSubject(v *string) *UserCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetDefaultGroupID sets the "default_group_id" field.
|
||||
func (_c *UserCreate) SetDefaultGroupID(v uuid.UUID) *UserCreate {
|
||||
_c.mutation.SetDefaultGroupID(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableDefaultGroupID sets the "default_group_id" field if the given value is not nil.
|
||||
func (_c *UserCreate) SetNillableDefaultGroupID(v *uuid.UUID) *UserCreate {
|
||||
if v != nil {
|
||||
_c.SetDefaultGroupID(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetID sets the "id" field.
|
||||
func (_c *UserCreate) SetID(v uuid.UUID) *UserCreate {
|
||||
_c.mutation.SetID(v)
|
||||
@@ -190,19 +176,15 @@ func (_c *UserCreate) SetNillableID(v *uuid.UUID) *UserCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddGroupIDs adds the "groups" edge to the Group entity by IDs.
|
||||
func (_c *UserCreate) AddGroupIDs(ids ...uuid.UUID) *UserCreate {
|
||||
_c.mutation.AddGroupIDs(ids...)
|
||||
// SetGroupID sets the "group" edge to the Group entity by ID.
|
||||
func (_c *UserCreate) SetGroupID(id uuid.UUID) *UserCreate {
|
||||
_c.mutation.SetGroupID(id)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddGroups adds the "groups" edges to the Group entity.
|
||||
func (_c *UserCreate) AddGroups(v ...*Group) *UserCreate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _c.AddGroupIDs(ids...)
|
||||
// SetGroup sets the "group" edge to the Group entity.
|
||||
func (_c *UserCreate) SetGroup(v *Group) *UserCreate {
|
||||
return _c.SetGroupID(v.ID)
|
||||
}
|
||||
|
||||
// AddAuthTokenIDs adds the "auth_tokens" edge to the AuthTokens entity by IDs.
|
||||
@@ -339,6 +321,9 @@ func (_c *UserCreate) check() error {
|
||||
return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "User.role": %w`, err)}
|
||||
}
|
||||
}
|
||||
if len(_c.mutation.GroupIDs()) == 0 {
|
||||
return &ValidationError{Name: "group", err: errors.New(`ent: missing required edge "User.group"`)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -418,16 +403,12 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(user.FieldOidcSubject, field.TypeString, value)
|
||||
_node.OidcSubject = &value
|
||||
}
|
||||
if value, ok := _c.mutation.DefaultGroupID(); ok {
|
||||
_spec.SetField(user.FieldDefaultGroupID, field.TypeUUID, value)
|
||||
_node.DefaultGroupID = &value
|
||||
}
|
||||
if nodes := _c.mutation.GroupsIDs(); len(nodes) > 0 {
|
||||
if nodes := _c.mutation.GroupIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: user.GroupTable,
|
||||
Columns: []string{user.GroupColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
@@ -436,6 +417,7 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_node.group_users = &nodes[0]
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
if nodes := _c.mutation.AuthTokensIDs(); len(nodes) > 0 {
|
||||
|
||||
102
backend/internal/data/ent/user_query.go
generated
102
backend/internal/data/ent/user_query.go
generated
@@ -27,9 +27,10 @@ type UserQuery struct {
|
||||
order []user.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.User
|
||||
withGroups *GroupQuery
|
||||
withGroup *GroupQuery
|
||||
withAuthTokens *AuthTokensQuery
|
||||
withNotifiers *NotifierQuery
|
||||
withFKs bool
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
@@ -66,8 +67,8 @@ func (_q *UserQuery) Order(o ...user.OrderOption) *UserQuery {
|
||||
return _q
|
||||
}
|
||||
|
||||
// QueryGroups chains the current query on the "groups" edge.
|
||||
func (_q *UserQuery) QueryGroups() *GroupQuery {
|
||||
// QueryGroup chains the current query on the "group" edge.
|
||||
func (_q *UserQuery) QueryGroup() *GroupQuery {
|
||||
query := (&GroupClient{config: _q.config}).Query()
|
||||
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
@@ -80,7 +81,7 @@ func (_q *UserQuery) QueryGroups() *GroupQuery {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(user.Table, user.FieldID, selector),
|
||||
sqlgraph.To(group.Table, group.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2M, false, user.GroupsTable, user.GroupsPrimaryKey...),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, user.GroupTable, user.GroupColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
@@ -324,7 +325,7 @@ func (_q *UserQuery) Clone() *UserQuery {
|
||||
order: append([]user.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.User{}, _q.predicates...),
|
||||
withGroups: _q.withGroups.Clone(),
|
||||
withGroup: _q.withGroup.Clone(),
|
||||
withAuthTokens: _q.withAuthTokens.Clone(),
|
||||
withNotifiers: _q.withNotifiers.Clone(),
|
||||
// clone intermediate query.
|
||||
@@ -333,14 +334,14 @@ func (_q *UserQuery) Clone() *UserQuery {
|
||||
}
|
||||
}
|
||||
|
||||
// WithGroups tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "groups" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *UserQuery) WithGroups(opts ...func(*GroupQuery)) *UserQuery {
|
||||
// WithGroup tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "group" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *UserQuery) WithGroup(opts ...func(*GroupQuery)) *UserQuery {
|
||||
query := (&GroupClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withGroups = query
|
||||
_q.withGroup = query
|
||||
return _q
|
||||
}
|
||||
|
||||
@@ -443,13 +444,20 @@ func (_q *UserQuery) prepareQuery(ctx context.Context) error {
|
||||
func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, error) {
|
||||
var (
|
||||
nodes = []*User{}
|
||||
withFKs = _q.withFKs
|
||||
_spec = _q.querySpec()
|
||||
loadedTypes = [3]bool{
|
||||
_q.withGroups != nil,
|
||||
_q.withGroup != nil,
|
||||
_q.withAuthTokens != nil,
|
||||
_q.withNotifiers != nil,
|
||||
}
|
||||
)
|
||||
if _q.withGroup != nil {
|
||||
withFKs = true
|
||||
}
|
||||
if withFKs {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, user.ForeignKeys...)
|
||||
}
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*User).scanValues(nil, columns)
|
||||
}
|
||||
@@ -468,10 +476,9 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
if query := _q.withGroups; query != nil {
|
||||
if err := _q.loadGroups(ctx, query, nodes,
|
||||
func(n *User) { n.Edges.Groups = []*Group{} },
|
||||
func(n *User, e *Group) { n.Edges.Groups = append(n.Edges.Groups, e) }); err != nil {
|
||||
if query := _q.withGroup; query != nil {
|
||||
if err := _q.loadGroup(ctx, query, nodes, nil,
|
||||
func(n *User, e *Group) { n.Edges.Group = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -492,63 +499,34 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (_q *UserQuery) loadGroups(ctx context.Context, query *GroupQuery, nodes []*User, init func(*User), assign func(*User, *Group)) error {
|
||||
edgeIDs := make([]driver.Value, len(nodes))
|
||||
byID := make(map[uuid.UUID]*User)
|
||||
nids := make(map[uuid.UUID]map[*User]struct{})
|
||||
for i, node := range nodes {
|
||||
edgeIDs[i] = node.ID
|
||||
byID[node.ID] = node
|
||||
if init != nil {
|
||||
init(node)
|
||||
func (_q *UserQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*User, init func(*User), assign func(*User, *Group)) error {
|
||||
ids := make([]uuid.UUID, 0, len(nodes))
|
||||
nodeids := make(map[uuid.UUID][]*User)
|
||||
for i := range nodes {
|
||||
if nodes[i].group_users == nil {
|
||||
continue
|
||||
}
|
||||
fk := *nodes[i].group_users
|
||||
if _, ok := nodeids[fk]; !ok {
|
||||
ids = append(ids, fk)
|
||||
}
|
||||
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||
}
|
||||
query.Where(func(s *sql.Selector) {
|
||||
joinT := sql.Table(user.GroupsTable)
|
||||
s.Join(joinT).On(s.C(group.FieldID), joinT.C(user.GroupsPrimaryKey[1]))
|
||||
s.Where(sql.InValues(joinT.C(user.GroupsPrimaryKey[0]), edgeIDs...))
|
||||
columns := s.SelectedColumns()
|
||||
s.Select(joinT.C(user.GroupsPrimaryKey[0]))
|
||||
s.AppendSelect(columns...)
|
||||
s.SetDistinct(false)
|
||||
})
|
||||
if err := query.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
|
||||
return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) {
|
||||
assign := spec.Assign
|
||||
values := spec.ScanValues
|
||||
spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
values, err := values(columns[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]any{new(uuid.UUID)}, values...), nil
|
||||
}
|
||||
spec.Assign = func(columns []string, values []any) error {
|
||||
outValue := *values[0].(*uuid.UUID)
|
||||
inValue := *values[1].(*uuid.UUID)
|
||||
if nids[inValue] == nil {
|
||||
nids[inValue] = map[*User]struct{}{byID[outValue]: {}}
|
||||
return assign(columns[1:], values[1:])
|
||||
}
|
||||
nids[inValue][byID[outValue]] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
})
|
||||
neighbors, err := withInterceptors[[]*Group](ctx, query, qr, query.inters)
|
||||
query.Where(group.IDIn(ids...))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
nodes, ok := nids[n.ID]
|
||||
nodes, ok := nodeids[n.ID]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected "groups" node returned %v`, n.ID)
|
||||
return fmt.Errorf(`unexpected foreign-key "group_users" returned %v`, n.ID)
|
||||
}
|
||||
for kn := range nodes {
|
||||
assign(kn, n)
|
||||
for i := range nodes {
|
||||
assign(nodes[i], n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
204
backend/internal/data/ent/user_update.go
generated
204
backend/internal/data/ent/user_update.go
generated
@@ -188,39 +188,15 @@ func (_u *UserUpdate) ClearOidcSubject() *UserUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetDefaultGroupID sets the "default_group_id" field.
|
||||
func (_u *UserUpdate) SetDefaultGroupID(v uuid.UUID) *UserUpdate {
|
||||
_u.mutation.SetDefaultGroupID(v)
|
||||
// SetGroupID sets the "group" edge to the Group entity by ID.
|
||||
func (_u *UserUpdate) SetGroupID(id uuid.UUID) *UserUpdate {
|
||||
_u.mutation.SetGroupID(id)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableDefaultGroupID sets the "default_group_id" field if the given value is not nil.
|
||||
func (_u *UserUpdate) SetNillableDefaultGroupID(v *uuid.UUID) *UserUpdate {
|
||||
if v != nil {
|
||||
_u.SetDefaultGroupID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearDefaultGroupID clears the value of the "default_group_id" field.
|
||||
func (_u *UserUpdate) ClearDefaultGroupID() *UserUpdate {
|
||||
_u.mutation.ClearDefaultGroupID()
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddGroupIDs adds the "groups" edge to the Group entity by IDs.
|
||||
func (_u *UserUpdate) AddGroupIDs(ids ...uuid.UUID) *UserUpdate {
|
||||
_u.mutation.AddGroupIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddGroups adds the "groups" edges to the Group entity.
|
||||
func (_u *UserUpdate) AddGroups(v ...*Group) *UserUpdate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.AddGroupIDs(ids...)
|
||||
// SetGroup sets the "group" edge to the Group entity.
|
||||
func (_u *UserUpdate) SetGroup(v *Group) *UserUpdate {
|
||||
return _u.SetGroupID(v.ID)
|
||||
}
|
||||
|
||||
// AddAuthTokenIDs adds the "auth_tokens" edge to the AuthTokens entity by IDs.
|
||||
@@ -258,27 +234,12 @@ func (_u *UserUpdate) Mutation() *UserMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// ClearGroups clears all "groups" edges to the Group entity.
|
||||
func (_u *UserUpdate) ClearGroups() *UserUpdate {
|
||||
_u.mutation.ClearGroups()
|
||||
// ClearGroup clears the "group" edge to the Group entity.
|
||||
func (_u *UserUpdate) ClearGroup() *UserUpdate {
|
||||
_u.mutation.ClearGroup()
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveGroupIDs removes the "groups" edge to Group entities by IDs.
|
||||
func (_u *UserUpdate) RemoveGroupIDs(ids ...uuid.UUID) *UserUpdate {
|
||||
_u.mutation.RemoveGroupIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveGroups removes "groups" edges to Group entities.
|
||||
func (_u *UserUpdate) RemoveGroups(v ...*Group) *UserUpdate {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.RemoveGroupIDs(ids...)
|
||||
}
|
||||
|
||||
// ClearAuthTokens clears all "auth_tokens" edges to the AuthTokens entity.
|
||||
func (_u *UserUpdate) ClearAuthTokens() *UserUpdate {
|
||||
_u.mutation.ClearAuthTokens()
|
||||
@@ -379,6 +340,9 @@ func (_u *UserUpdate) check() error {
|
||||
return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "User.role": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _u.mutation.GroupCleared() && len(_u.mutation.GroupIDs()) > 0 {
|
||||
return errors.New(`ent: clearing a required unique edge "User.group"`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -436,18 +400,12 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.OidcSubjectCleared() {
|
||||
_spec.ClearField(user.FieldOidcSubject, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.DefaultGroupID(); ok {
|
||||
_spec.SetField(user.FieldDefaultGroupID, field.TypeUUID, value)
|
||||
}
|
||||
if _u.mutation.DefaultGroupIDCleared() {
|
||||
_spec.ClearField(user.FieldDefaultGroupID, field.TypeUUID)
|
||||
}
|
||||
if _u.mutation.GroupsCleared() {
|
||||
if _u.mutation.GroupCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: user.GroupTable,
|
||||
Columns: []string{user.GroupColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
@@ -455,28 +413,12 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedGroupsIDs(); len(nodes) > 0 && !_u.mutation.GroupsCleared() {
|
||||
if nodes := _u.mutation.GroupIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.GroupsIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: user.GroupTable,
|
||||
Columns: []string{user.GroupColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
@@ -753,39 +695,15 @@ func (_u *UserUpdateOne) ClearOidcSubject() *UserUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetDefaultGroupID sets the "default_group_id" field.
|
||||
func (_u *UserUpdateOne) SetDefaultGroupID(v uuid.UUID) *UserUpdateOne {
|
||||
_u.mutation.SetDefaultGroupID(v)
|
||||
// SetGroupID sets the "group" edge to the Group entity by ID.
|
||||
func (_u *UserUpdateOne) SetGroupID(id uuid.UUID) *UserUpdateOne {
|
||||
_u.mutation.SetGroupID(id)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableDefaultGroupID sets the "default_group_id" field if the given value is not nil.
|
||||
func (_u *UserUpdateOne) SetNillableDefaultGroupID(v *uuid.UUID) *UserUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetDefaultGroupID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearDefaultGroupID clears the value of the "default_group_id" field.
|
||||
func (_u *UserUpdateOne) ClearDefaultGroupID() *UserUpdateOne {
|
||||
_u.mutation.ClearDefaultGroupID()
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddGroupIDs adds the "groups" edge to the Group entity by IDs.
|
||||
func (_u *UserUpdateOne) AddGroupIDs(ids ...uuid.UUID) *UserUpdateOne {
|
||||
_u.mutation.AddGroupIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddGroups adds the "groups" edges to the Group entity.
|
||||
func (_u *UserUpdateOne) AddGroups(v ...*Group) *UserUpdateOne {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.AddGroupIDs(ids...)
|
||||
// SetGroup sets the "group" edge to the Group entity.
|
||||
func (_u *UserUpdateOne) SetGroup(v *Group) *UserUpdateOne {
|
||||
return _u.SetGroupID(v.ID)
|
||||
}
|
||||
|
||||
// AddAuthTokenIDs adds the "auth_tokens" edge to the AuthTokens entity by IDs.
|
||||
@@ -823,27 +741,12 @@ func (_u *UserUpdateOne) Mutation() *UserMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// ClearGroups clears all "groups" edges to the Group entity.
|
||||
func (_u *UserUpdateOne) ClearGroups() *UserUpdateOne {
|
||||
_u.mutation.ClearGroups()
|
||||
// ClearGroup clears the "group" edge to the Group entity.
|
||||
func (_u *UserUpdateOne) ClearGroup() *UserUpdateOne {
|
||||
_u.mutation.ClearGroup()
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveGroupIDs removes the "groups" edge to Group entities by IDs.
|
||||
func (_u *UserUpdateOne) RemoveGroupIDs(ids ...uuid.UUID) *UserUpdateOne {
|
||||
_u.mutation.RemoveGroupIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveGroups removes "groups" edges to Group entities.
|
||||
func (_u *UserUpdateOne) RemoveGroups(v ...*Group) *UserUpdateOne {
|
||||
ids := make([]uuid.UUID, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.RemoveGroupIDs(ids...)
|
||||
}
|
||||
|
||||
// ClearAuthTokens clears all "auth_tokens" edges to the AuthTokens entity.
|
||||
func (_u *UserUpdateOne) ClearAuthTokens() *UserUpdateOne {
|
||||
_u.mutation.ClearAuthTokens()
|
||||
@@ -957,6 +860,9 @@ func (_u *UserUpdateOne) check() error {
|
||||
return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "User.role": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _u.mutation.GroupCleared() && len(_u.mutation.GroupIDs()) > 0 {
|
||||
return errors.New(`ent: clearing a required unique edge "User.group"`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1031,18 +937,12 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
||||
if _u.mutation.OidcSubjectCleared() {
|
||||
_spec.ClearField(user.FieldOidcSubject, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.DefaultGroupID(); ok {
|
||||
_spec.SetField(user.FieldDefaultGroupID, field.TypeUUID, value)
|
||||
}
|
||||
if _u.mutation.DefaultGroupIDCleared() {
|
||||
_spec.ClearField(user.FieldDefaultGroupID, field.TypeUUID)
|
||||
}
|
||||
if _u.mutation.GroupsCleared() {
|
||||
if _u.mutation.GroupCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: user.GroupTable,
|
||||
Columns: []string{user.GroupColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
@@ -1050,28 +950,12 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedGroupsIDs(); len(nodes) > 0 && !_u.mutation.GroupsCleared() {
|
||||
if nodes := _u.mutation.GroupIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.GroupsIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2M,
|
||||
Inverse: false,
|
||||
Table: user.GroupsTable,
|
||||
Columns: user.GroupsPrimaryKey,
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: user.GroupTable,
|
||||
Columns: []string{user.GroupColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeUUID),
|
||||
|
||||
@@ -31,4 +31,5 @@ func Migrations(dialect string) (embed.FS, error) {
|
||||
return embed.FS{}, fmt.Errorf("unknown sql dialect: %s", dialect)
|
||||
}
|
||||
// This should never get hit, but just in case
|
||||
return sqliteFiles, nil
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
-- +goose Up
|
||||
-- Create user_groups junction table for M:M relationship
|
||||
CREATE TABLE IF NOT EXISTS "user_groups" (
|
||||
"user_id" uuid NOT NULL,
|
||||
"group_id" uuid NOT NULL,
|
||||
PRIMARY KEY ("user_id", "group_id"),
|
||||
CONSTRAINT "user_groups_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "user_groups_group_id" FOREIGN KEY ("group_id") REFERENCES "groups" ("id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Migrate existing user->group relationships to the junction table
|
||||
INSERT INTO "user_groups" ("user_id", "group_id")
|
||||
SELECT "id", "group_users" FROM "users" WHERE "group_users" IS NOT NULL;
|
||||
|
||||
-- Add default_group_id column to users table
|
||||
ALTER TABLE "users" ADD COLUMN "default_group_id" uuid;
|
||||
|
||||
-- Set default_group_id to the user's current group
|
||||
UPDATE "users" SET "default_group_id" = "group_users" WHERE "group_users" IS NOT NULL;
|
||||
|
||||
-- Drop the old group_users foreign key constraint and column
|
||||
ALTER TABLE "users" DROP CONSTRAINT "users_groups_users";
|
||||
ALTER TABLE "users" DROP COLUMN "group_users";
|
||||
|
||||
-- Add foreign key constraint for default_group_id
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_groups_users_default" FOREIGN KEY ("default_group_id") REFERENCES "groups" ("id") ON UPDATE NO ACTION ON DELETE SET NULL;
|
||||
|
||||
-- +goose Down
|
||||
-- Recreate group_users column with foreign key
|
||||
ALTER TABLE "users" ADD COLUMN "group_users" uuid;
|
||||
|
||||
-- Restore the group_users values from user_groups (using the default_group_id or first entry)
|
||||
UPDATE "users"
|
||||
SET "group_users" = COALESCE("default_group_id", (
|
||||
SELECT "group_id" FROM "user_groups" WHERE "user_id" = "users"."id" LIMIT 1
|
||||
));
|
||||
|
||||
-- Drop the default_group_id foreign key and column
|
||||
ALTER TABLE "users" DROP CONSTRAINT "users_groups_users_default";
|
||||
ALTER TABLE "users" DROP COLUMN "default_group_id";
|
||||
|
||||
-- Add back the original foreign key constraint
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_groups_users" FOREIGN KEY ("group_users") REFERENCES "groups" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
|
||||
|
||||
-- Drop the junction table
|
||||
DROP TABLE IF EXISTS "user_groups";
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose no transaction
|
||||
-- Turn off foreign key constraints because otherwise we'll wipe notifiers out of the database when dropping the older users table
|
||||
PRAGMA foreign_keys=OFF;
|
||||
-- Create user_groups junction table for M:M relationship
|
||||
CREATE TABLE IF NOT EXISTS user_groups (
|
||||
user_id UUID NOT NULL,
|
||||
group_id UUID NOT NULL,
|
||||
PRIMARY KEY (user_id, group_id),
|
||||
CONSTRAINT user_groups_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT user_groups_group_id FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Migrate existing user->group relationships to the junction table
|
||||
INSERT INTO user_groups (user_id, group_id)
|
||||
SELECT id, group_users FROM users WHERE group_users IS NOT NULL;
|
||||
|
||||
-- Add default_group_id column to users table
|
||||
ALTER TABLE users ADD COLUMN default_group_id UUID;
|
||||
|
||||
-- Set default_group_id to the user's current group
|
||||
UPDATE users SET default_group_id = group_users WHERE group_users IS NOT NULL;
|
||||
|
||||
-- Add foreign key constraint for default_group_id
|
||||
CREATE TABLE users_new (
|
||||
id UUID NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password TEXT,
|
||||
is_superuser BOOLEAN NOT NULL DEFAULT false,
|
||||
superuser BOOLEAN NOT NULL DEFAULT false,
|
||||
role TEXT NOT NULL DEFAULT 'user',
|
||||
activated_on DATETIME,
|
||||
oidc_issuer TEXT,
|
||||
oidc_subject TEXT,
|
||||
default_group_id UUID,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT users_groups_users_default FOREIGN KEY (default_group_id) REFERENCES groups(id) ON DELETE SET NULL,
|
||||
UNIQUE (oidc_issuer, oidc_subject)
|
||||
);
|
||||
|
||||
-- Copy data from old table to new table
|
||||
INSERT INTO users_new (
|
||||
id, created_at, updated_at, name, email, password, is_superuser, superuser, role,
|
||||
activated_on, oidc_issuer, oidc_subject, default_group_id
|
||||
)
|
||||
SELECT
|
||||
id, created_at, updated_at, name, email, password, is_superuser, superuser, role,
|
||||
activated_on, oidc_issuer, oidc_subject, default_group_id
|
||||
FROM users;
|
||||
|
||||
-- Drop old indexes
|
||||
DROP INDEX IF EXISTS users_email_key;
|
||||
DROP INDEX IF EXISTS users_oidc_issuer_subject_key;
|
||||
|
||||
-- Drop old table
|
||||
DROP TABLE users;
|
||||
|
||||
-- Rename new table to users
|
||||
ALTER TABLE users_new RENAME TO users;
|
||||
|
||||
-- Recreate indexes
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_email_key ON users(email);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_oidc_issuer_subject_key ON users(oidc_issuer, oidc_subject);
|
||||
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -2,7 +2,6 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||
"log"
|
||||
"os"
|
||||
@@ -30,7 +29,7 @@ func bootstrap() {
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
tGroup, err = tRepos.Groups.GroupCreate(ctx, "test-group", uuid.Nil)
|
||||
tGroup, err = tRepos.Groups.GroupCreate(ctx, "test-group")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, gid uuid.UUID,
|
||||
func (r *GroupRepository) StatsGroup(ctx context.Context, gid uuid.UUID) (GroupStatistics, error) {
|
||||
q := `
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM user_groups WHERE group_id = $2) AS total_users,
|
||||
(SELECT COUNT(*) FROM users WHERE group_users = $2) AS total_users,
|
||||
(SELECT COUNT(*) FROM items WHERE group_items = $2 AND items.archived = false) AS total_items,
|
||||
(SELECT COUNT(*) FROM locations WHERE group_locations = $2) AS total_locations,
|
||||
(SELECT COUNT(*) FROM labels WHERE group_labels = $2) AS total_labels,
|
||||
@@ -252,15 +252,10 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, gid uuid.UUID) (GroupS
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (r *GroupRepository) GroupCreate(ctx context.Context, name string, userID uuid.UUID) (Group, error) {
|
||||
createQuery := r.db.Group.Create().SetName(name)
|
||||
|
||||
// Only link user if a valid user ID is provided
|
||||
if userID != uuid.Nil {
|
||||
createQuery = createQuery.AddUserIDs(userID)
|
||||
}
|
||||
|
||||
return r.groupMapper.MapErr(createQuery.Save(ctx))
|
||||
func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group, error) {
|
||||
return r.groupMapper.MapErr(r.db.Group.Create().
|
||||
SetName(name).
|
||||
Save(ctx))
|
||||
}
|
||||
|
||||
func (r *GroupRepository) GroupUpdate(ctx context.Context, id uuid.UUID, data GroupUpdate) (Group, error) {
|
||||
@@ -276,10 +271,6 @@ func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, e
|
||||
return r.groupMapper.MapErr(r.db.Group.Get(ctx, id))
|
||||
}
|
||||
|
||||
func (r *GroupRepository) GroupDelete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.Group.DeleteOneID(id).Exec(ctx)
|
||||
}
|
||||
|
||||
func (r *GroupRepository) InvitationGet(ctx context.Context, token []byte) (GroupInvitation, error) {
|
||||
return r.invitationMapper.MapErr(r.db.GroupInvitationToken.Query().
|
||||
Where(groupinvitationtoken.Token(token)).
|
||||
@@ -287,18 +278,6 @@ func (r *GroupRepository) InvitationGet(ctx context.Context, token []byte) (Grou
|
||||
Only(ctx))
|
||||
}
|
||||
|
||||
func (r *GroupRepository) InvitationGetAll(ctx context.Context, groupID uuid.UUID) ([]GroupInvitation, error) {
|
||||
invitations, err := r.db.GroupInvitationToken.Query().
|
||||
Where(groupinvitationtoken.HasGroupWith(group.ID(groupID))).
|
||||
WithGroup().
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.invitationMapper.MapEach(invitations), nil
|
||||
}
|
||||
|
||||
func (r *GroupRepository) InvitationCreate(ctx context.Context, groupID uuid.UUID, invite GroupInvitationCreate) (GroupInvitation, error) {
|
||||
entity, err := r.db.GroupInvitationToken.Create().
|
||||
SetGroupID(groupID).
|
||||
@@ -329,11 +308,3 @@ func (r *GroupRepository) InvitationPurge(ctx context.Context) (amount int, err
|
||||
|
||||
return q.Exec(ctx)
|
||||
}
|
||||
|
||||
func (r *GroupRepository) AddMember(ctx context.Context, groupID, userID uuid.UUID) error {
|
||||
return r.db.Group.UpdateOneID(groupID).AddUserIDs(userID).Exec(ctx)
|
||||
}
|
||||
|
||||
func (r *GroupRepository) RemoveMember(ctx context.Context, groupID, userID uuid.UUID) error {
|
||||
return r.db.Group.UpdateOneID(groupID).RemoveUserIDs(userID).Exec(ctx)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Group_Create(t *testing.T) {
|
||||
g, err := tRepos.Groups.GroupCreate(context.Background(), "test", uuid.Nil)
|
||||
g, err := tRepos.Groups.GroupCreate(context.Background(), "test")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "test", g.Name)
|
||||
@@ -22,7 +21,7 @@ func Test_Group_Create(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Group_Update(t *testing.T) {
|
||||
g, err := tRepos.Groups.GroupCreate(context.Background(), "test", uuid.Nil)
|
||||
g, err := tRepos.Groups.GroupCreate(context.Background(), "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
g, err = tRepos.Groups.GroupUpdate(context.Background(), g.ID, GroupUpdate{
|
||||
|
||||
@@ -114,17 +114,17 @@ func (r *AttachmentRepo) fullPath(relativePath string) string {
|
||||
// Normalize path separators to forward slashes for blob storage
|
||||
// The blob library expects forward slashes in keys regardless of OS
|
||||
normalizedRelativePath := normalizePath(relativePath)
|
||||
|
||||
|
||||
// Always use forward slashes when joining paths for blob storage
|
||||
if r.storage.PrefixPath == "" {
|
||||
return normalizedRelativePath
|
||||
}
|
||||
normalizedPrefix := normalizePath(r.storage.PrefixPath)
|
||||
|
||||
|
||||
if normalizedPrefix == "" {
|
||||
return normalizedRelativePath
|
||||
}
|
||||
|
||||
|
||||
return fmt.Sprintf("%s/%s", normalizedPrefix, normalizedRelativePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -290,25 +290,25 @@ func TestAttachmentRepo_PathNormalization(t *testing.T) {
|
||||
PrefixPath: ".data",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
testGUID := uuid.MustParse("eb6bf410-a1a8-478d-a803-ca3948368a0c")
|
||||
testHash := "f295eb01-18a9-4631-a797-70bd9623edd4.png"
|
||||
|
||||
|
||||
// Test path() method - should always return forward slashes
|
||||
relativePath := repo.path(testGUID, testHash)
|
||||
assert.Equal(t, "eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png", relativePath)
|
||||
assert.NotContains(t, relativePath, "\\", "path() should not contain backslashes")
|
||||
|
||||
|
||||
// Test fullPath() with forward slash input (from database)
|
||||
fullPath := repo.fullPath("eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png")
|
||||
assert.Equal(t, ".data/eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png", fullPath)
|
||||
assert.NotContains(t, fullPath, "\\", "fullPath() should not contain backslashes")
|
||||
|
||||
|
||||
// Test fullPath() with backslash input (legacy Windows paths from old database)
|
||||
fullPathWithBackslash := repo.fullPath("eb6bf410-a1a8-478d-a803-ca3948368a0c\\documents\\f295eb01-18a9-4631-a797-70bd9623edd4.png")
|
||||
assert.Equal(t, ".data/eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png", fullPathWithBackslash)
|
||||
assert.NotContains(t, fullPathWithBackslash, "\\", "fullPath() should normalize backslashes to forward slashes")
|
||||
|
||||
|
||||
// Test with Windows-style prefix path
|
||||
repoWindows := &AttachmentRepo{
|
||||
storage: config.Storage{
|
||||
@@ -317,7 +317,7 @@ func TestAttachmentRepo_PathNormalization(t *testing.T) {
|
||||
}
|
||||
fullPathWindows := repoWindows.fullPath("eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png")
|
||||
assert.NotContains(t, fullPathWindows, "\\", "fullPath() should normalize Windows paths")
|
||||
|
||||
|
||||
// Test empty prefix
|
||||
repoNoPrefix := &AttachmentRepo{
|
||||
storage: config.Storage{
|
||||
@@ -326,7 +326,7 @@ func TestAttachmentRepo_PathNormalization(t *testing.T) {
|
||||
}
|
||||
fullPathNoPrefix := repoNoPrefix.fullPath("eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png")
|
||||
assert.Equal(t, "eb6bf410-a1a8-478d-a803-ca3948368a0c/documents/f295eb01-18a9-4631-a797-70bd9623edd4.png", fullPathNoPrefix)
|
||||
|
||||
|
||||
// Test with single slash prefix (like in tests)
|
||||
repoSlashPrefix := &AttachmentRepo{
|
||||
storage: config.Storage{
|
||||
|
||||
@@ -43,18 +43,18 @@ type (
|
||||
Notes string `json:"notes" validate:"max=1000"`
|
||||
|
||||
// Default values for items
|
||||
DefaultQuantity *int `json:"defaultQuantity,omitempty" extensions:"x-nullable"`
|
||||
DefaultQuantity *int `json:"defaultQuantity,omitempty" extensions:"x-nullable"`
|
||||
DefaultInsured bool `json:"defaultInsured"`
|
||||
DefaultName *string `json:"defaultName,omitempty" validate:"omitempty,max=255" extensions:"x-nullable"`
|
||||
DefaultDescription *string `json:"defaultDescription,omitempty" validate:"omitempty,max=1000" extensions:"x-nullable"`
|
||||
DefaultManufacturer *string `json:"defaultManufacturer,omitempty" validate:"omitempty,max=255" extensions:"x-nullable"`
|
||||
DefaultModelNumber *string `json:"defaultModelNumber,omitempty" validate:"omitempty,max=255" extensions:"x-nullable"`
|
||||
DefaultName *string `json:"defaultName,omitempty" extensions:"x-nullable" validate:"omitempty,max=255"`
|
||||
DefaultDescription *string `json:"defaultDescription,omitempty" extensions:"x-nullable" validate:"omitempty,max=1000"`
|
||||
DefaultManufacturer *string `json:"defaultManufacturer,omitempty" extensions:"x-nullable" validate:"omitempty,max=255"`
|
||||
DefaultModelNumber *string `json:"defaultModelNumber,omitempty" extensions:"x-nullable" validate:"omitempty,max=255"`
|
||||
DefaultLifetimeWarranty bool `json:"defaultLifetimeWarranty"`
|
||||
DefaultWarrantyDetails *string `json:"defaultWarrantyDetails,omitempty" validate:"omitempty,max=1000" extensions:"x-nullable"`
|
||||
DefaultWarrantyDetails *string `json:"defaultWarrantyDetails,omitempty" extensions:"x-nullable" validate:"omitempty,max=1000"`
|
||||
|
||||
// Default location and labels
|
||||
DefaultLocationID uuid.UUID `json:"defaultLocationId,omitempty" extensions:"x-nullable"`
|
||||
DefaultLabelIDs *[]uuid.UUID `json:"defaultLabelIds,omitempty" extensions:"x-nullable"`
|
||||
DefaultLabelIDs *[]uuid.UUID `json:"defaultLabelIds,omitempty" extensions:"x-nullable"`
|
||||
|
||||
// Metadata flags
|
||||
IncludeWarrantyFields bool `json:"includeWarrantyFields"`
|
||||
@@ -72,18 +72,18 @@ type (
|
||||
Notes string `json:"notes" validate:"max=1000"`
|
||||
|
||||
// Default values for items
|
||||
DefaultQuantity *int `json:"defaultQuantity,omitempty" extensions:"x-nullable"`
|
||||
DefaultQuantity *int `json:"defaultQuantity,omitempty" extensions:"x-nullable"`
|
||||
DefaultInsured bool `json:"defaultInsured"`
|
||||
DefaultName *string `json:"defaultName,omitempty" validate:"omitempty,max=255" extensions:"x-nullable"`
|
||||
DefaultDescription *string `json:"defaultDescription,omitempty" validate:"omitempty,max=1000" extensions:"x-nullable"`
|
||||
DefaultManufacturer *string `json:"defaultManufacturer,omitempty" validate:"omitempty,max=255" extensions:"x-nullable"`
|
||||
DefaultModelNumber *string `json:"defaultModelNumber,omitempty" validate:"omitempty,max=255" extensions:"x-nullable"`
|
||||
DefaultName *string `json:"defaultName,omitempty" extensions:"x-nullable" validate:"omitempty,max=255"`
|
||||
DefaultDescription *string `json:"defaultDescription,omitempty" extensions:"x-nullable" validate:"omitempty,max=1000"`
|
||||
DefaultManufacturer *string `json:"defaultManufacturer,omitempty" extensions:"x-nullable" validate:"omitempty,max=255"`
|
||||
DefaultModelNumber *string `json:"defaultModelNumber,omitempty" extensions:"x-nullable" validate:"omitempty,max=255"`
|
||||
DefaultLifetimeWarranty bool `json:"defaultLifetimeWarranty"`
|
||||
DefaultWarrantyDetails *string `json:"defaultWarrantyDetails,omitempty" validate:"omitempty,max=1000" extensions:"x-nullable"`
|
||||
DefaultWarrantyDetails *string `json:"defaultWarrantyDetails,omitempty" extensions:"x-nullable" validate:"omitempty,max=1000"`
|
||||
|
||||
// Default location and labels
|
||||
DefaultLocationID uuid.UUID `json:"defaultLocationId,omitempty" extensions:"x-nullable"`
|
||||
DefaultLabelIDs *[]uuid.UUID `json:"defaultLabelIds,omitempty" extensions:"x-nullable"`
|
||||
DefaultLabelIDs *[]uuid.UUID `json:"defaultLabelIds,omitempty" extensions:"x-nullable"`
|
||||
|
||||
// Metadata flags
|
||||
IncludeWarrantyFields bool `json:"includeWarrantyFields"`
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestItemsRepository_AccentInsensitiveSearch(t *testing.T) {
|
||||
if tc.shouldMatch {
|
||||
// If it should match, then either the original query should match
|
||||
// or the normalized query should match when applied to the stored data
|
||||
assert.NotEmpty(t, normalizedSearch, "Normalized search should not be empty")
|
||||
assert.NotEqual(t, "", normalizedSearch, "Normalized search should not be empty")
|
||||
|
||||
// The key insight is that we're searching with both the original and normalized queries
|
||||
// So "electrónica" will be found when searching for "electronica" because:
|
||||
|
||||
@@ -313,7 +313,7 @@ func TestItemRepository_GetAllCustomFields(t *testing.T) {
|
||||
|
||||
// Test getting all values from field
|
||||
{
|
||||
results, err := tRepos.Items.GetAllCustomFieldValues(context.Background(), tUser.DefaultGroupID, names[0])
|
||||
results, err := tRepos.Items.GetAllCustomFieldValues(context.Background(), tUser.GroupID, names[0])
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, values[:1], results)
|
||||
@@ -400,33 +400,33 @@ func TestItemsRepository_DeleteByGroupWithAttachments(t *testing.T) {
|
||||
|
||||
func TestItemsRepository_WipeInventory(t *testing.T) {
|
||||
// Create test data: items, labels, locations, and maintenance entries
|
||||
|
||||
|
||||
// Create locations
|
||||
loc1, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Location 1",
|
||||
Description: "Test location for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
loc2, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Location 2",
|
||||
Description: "Another test location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Create labels
|
||||
label1, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Label 1",
|
||||
Description: "Test label for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
label2, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Label 2",
|
||||
Description: "Another test label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Create items
|
||||
item1, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Item 1",
|
||||
@@ -435,7 +435,7 @@ func TestItemsRepository_WipeInventory(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label1.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
item2, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Item 2",
|
||||
Description: "Another test item",
|
||||
@@ -443,7 +443,7 @@ func TestItemsRepository_WipeInventory(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label2.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Create maintenance entries for items
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item1.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
@@ -452,7 +452,7 @@ func TestItemsRepository_WipeInventory(t *testing.T) {
|
||||
Cost: 100.0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item2.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Test Maintenance 2",
|
||||
@@ -460,40 +460,40 @@ func TestItemsRepository_WipeInventory(t *testing.T) {
|
||||
Cost: 200.0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Test 1: Wipe inventory with all options enabled
|
||||
t.Run("wipe all including labels, locations, and maintenance", func(t *testing.T) {
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, true, true, true)
|
||||
require.NoError(t, err)
|
||||
assert.Positive(t, deleted, "Should have deleted at least some entities")
|
||||
|
||||
assert.Greater(t, deleted, 0, "Should have deleted at least some entities")
|
||||
|
||||
// Verify items are deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item1.ID)
|
||||
require.Error(t, err, "Item 1 should be deleted")
|
||||
|
||||
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item2.ID)
|
||||
require.Error(t, err, "Item 2 should be deleted")
|
||||
|
||||
|
||||
// Verify maintenance entries are deleted (query by item ID, should return empty)
|
||||
maint1List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maint1List, "Maintenance entry 1 should be deleted")
|
||||
|
||||
|
||||
maint2List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item2.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maint2List, "Maintenance entry 2 should be deleted")
|
||||
|
||||
|
||||
// Verify labels are deleted
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label1.ID)
|
||||
require.Error(t, err, "Label 1 should be deleted")
|
||||
|
||||
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label2.ID)
|
||||
require.Error(t, err, "Label 2 should be deleted")
|
||||
|
||||
|
||||
// Verify locations are deleted
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc1.ID)
|
||||
require.Error(t, err, "Location 1 should be deleted")
|
||||
|
||||
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc2.ID)
|
||||
require.Error(t, err, "Location 2 should be deleted")
|
||||
})
|
||||
@@ -506,13 +506,13 @@ func TestItemsRepository_WipeInventory_OnlyItems(t *testing.T) {
|
||||
Description: "Test location for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
label, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Label",
|
||||
Description: "Test label for wipe test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
item, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Item",
|
||||
Description: "Test item for wipe test",
|
||||
@@ -520,7 +520,7 @@ func TestItemsRepository_WipeInventory_OnlyItems(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Test Maintenance",
|
||||
@@ -528,30 +528,31 @@ func TestItemsRepository_WipeInventory_OnlyItems(t *testing.T) {
|
||||
Cost: 100.0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Test: Wipe inventory with only items (no labels, locations, or maintenance)
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, false, false, false)
|
||||
require.NoError(t, err)
|
||||
assert.Positive(t, deleted, "Should have deleted at least the item")
|
||||
|
||||
assert.Greater(t, deleted, 0, "Should have deleted at least the item")
|
||||
|
||||
// Verify item is deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item.ID)
|
||||
require.Error(t, err, "Item should be deleted")
|
||||
|
||||
|
||||
// Verify maintenance entry is deleted due to cascade
|
||||
maintList, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maintList, "Maintenance entry should be cascade deleted with item")
|
||||
|
||||
|
||||
// Verify label still exists
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
require.NoError(t, err, "Label should still exist")
|
||||
|
||||
|
||||
// Verify location still exists
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc.ID)
|
||||
require.NoError(t, err, "Location should still exist")
|
||||
|
||||
|
||||
// Cleanup
|
||||
_ = tRepos.Labels.DeleteByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
_ = tRepos.Locations.delete(context.Background(), loc.ID)
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (U
|
||||
Where(authtokens.ExpiresAtGTE(time.Now())).
|
||||
WithUser().
|
||||
QueryUser().
|
||||
WithGroups().
|
||||
WithGroup().
|
||||
Only(ctx)
|
||||
if err != nil {
|
||||
return UserOut{}, err
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/user"
|
||||
)
|
||||
|
||||
@@ -18,12 +17,12 @@ type (
|
||||
// in the database. It should to create users from an API unless the user has
|
||||
// rights to create SuperUsers. For regular user in data use the UserIn struct.
|
||||
UserCreate struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
IsSuperuser bool `json:"isSuperUser"`
|
||||
DefaultGroupID uuid.UUID `json:"defaultGroupID"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
IsSuperuser bool `json:"isSuperuser"`
|
||||
GroupID uuid.UUID `json:"groupID"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
}
|
||||
|
||||
UserUpdate struct {
|
||||
@@ -32,16 +31,16 @@ type (
|
||||
}
|
||||
|
||||
UserOut struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
IsSuperuser bool `json:"isSuperuser"`
|
||||
DefaultGroupID uuid.UUID `json:"defaultGroupId"`
|
||||
GroupIDs []uuid.UUID `json:"groupIds"`
|
||||
PasswordHash string `json:"-"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
OidcIssuer *string `json:"oidcIssuer"`
|
||||
OidcSubject *string `json:"oidcSubject"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
IsSuperuser bool `json:"isSuperuser"`
|
||||
GroupID uuid.UUID `json:"groupId"`
|
||||
GroupName string `json:"groupName"`
|
||||
PasswordHash string `json:"-"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
OidcIssuer *string `json:"oidcIssuer"`
|
||||
OidcSubject *string `json:"oidcSubject"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -56,55 +55,37 @@ func mapUserOut(user *ent.User) UserOut {
|
||||
passwordHash = *user.Password
|
||||
}
|
||||
|
||||
groupIDs := make([]uuid.UUID, len(user.Edges.Groups))
|
||||
for i, g := range user.Edges.Groups {
|
||||
groupIDs[i] = g.ID
|
||||
}
|
||||
|
||||
// Get the default group ID, handling the optional pointer
|
||||
defaultGroupID := uuid.Nil
|
||||
if user.DefaultGroupID != nil {
|
||||
defaultGroupID = *user.DefaultGroupID
|
||||
}
|
||||
|
||||
return UserOut{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
IsSuperuser: user.IsSuperuser,
|
||||
DefaultGroupID: defaultGroupID,
|
||||
GroupIDs: groupIDs,
|
||||
PasswordHash: passwordHash,
|
||||
IsOwner: user.Role == "owner",
|
||||
OidcIssuer: user.OidcIssuer,
|
||||
OidcSubject: user.OidcSubject,
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
IsSuperuser: user.IsSuperuser,
|
||||
GroupID: user.Edges.Group.ID,
|
||||
GroupName: user.Edges.Group.Name,
|
||||
PasswordHash: passwordHash,
|
||||
IsOwner: user.Role == "owner",
|
||||
OidcIssuer: user.OidcIssuer,
|
||||
OidcSubject: user.OidcSubject,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetOneID(ctx context.Context, id uuid.UUID) (UserOut, error) {
|
||||
return mapUserOutErr(r.db.User.Query().
|
||||
Where(user.ID(id)).
|
||||
WithGroups().
|
||||
WithGroup().
|
||||
Only(ctx))
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetOneEmail(ctx context.Context, email string) (UserOut, error) {
|
||||
return mapUserOutErr(r.db.User.Query().
|
||||
Where(user.EmailEqualFold(email)).
|
||||
WithGroups().
|
||||
Only(ctx),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetOneEmailNoEdges(ctx context.Context, email string) (UserOut, error) {
|
||||
return mapUserOutErr(r.db.User.Query().
|
||||
Where(user.EmailEqualFold(email)).
|
||||
WithGroup().
|
||||
Only(ctx),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetAll(ctx context.Context) ([]UserOut, error) {
|
||||
return mapUsersOutErr(r.db.User.Query().WithGroups().All(ctx))
|
||||
return mapUsersOutErr(r.db.User.Query().WithGroup().All(ctx))
|
||||
}
|
||||
|
||||
func (r *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, error) {
|
||||
@@ -118,9 +99,8 @@ func (r *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, e
|
||||
SetName(usr.Name).
|
||||
SetEmail(usr.Email).
|
||||
SetIsSuperuser(usr.IsSuperuser).
|
||||
SetDefaultGroupID(usr.DefaultGroupID).
|
||||
SetRole(role).
|
||||
AddGroupIDs(usr.DefaultGroupID)
|
||||
SetGroupID(usr.GroupID).
|
||||
SetRole(role)
|
||||
|
||||
// Only set password if provided (non-nil)
|
||||
if usr.Password != nil {
|
||||
@@ -146,11 +126,10 @@ func (r *UserRepository) CreateWithOIDC(ctx context.Context, usr UserCreate, iss
|
||||
SetName(usr.Name).
|
||||
SetEmail(usr.Email).
|
||||
SetIsSuperuser(usr.IsSuperuser).
|
||||
SetDefaultGroupID(usr.DefaultGroupID).
|
||||
SetGroupID(usr.GroupID).
|
||||
SetRole(role).
|
||||
SetOidcIssuer(issuer).
|
||||
SetOidcSubject(subject).
|
||||
AddGroupIDs(usr.DefaultGroupID)
|
||||
SetOidcSubject(subject)
|
||||
|
||||
if usr.Password != nil {
|
||||
createQuery = createQuery.SetPassword(*usr.Password)
|
||||
@@ -204,13 +183,6 @@ func (r *UserRepository) SetOIDCIdentity(ctx context.Context, uid uuid.UUID, iss
|
||||
func (r *UserRepository) GetOneOIDC(ctx context.Context, issuer, subject string) (UserOut, error) {
|
||||
return mapUserOutErr(r.db.User.Query().
|
||||
Where(user.OidcIssuerEQ(issuer), user.OidcSubjectEQ(subject)).
|
||||
WithGroups().
|
||||
WithGroup().
|
||||
Only(ctx))
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetUsersByGroupID(ctx context.Context, gid uuid.UUID) ([]UserOut, error) {
|
||||
return mapUsersOutErr(r.db.User.Query().
|
||||
WithGroups().
|
||||
Where(user.HasGroupsWith(group.ID(gid))).
|
||||
All(ctx))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -12,11 +11,11 @@ import (
|
||||
func userFactory() UserCreate {
|
||||
password := fk.Str(10)
|
||||
return UserCreate{
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: &password,
|
||||
IsSuperuser: fk.Bool(),
|
||||
DefaultGroupID: tGroup.ID,
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: &password,
|
||||
IsSuperuser: fk.Bool(),
|
||||
GroupID: tGroup.ID,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +87,7 @@ func TestUserRepo_GetAll(t *testing.T) {
|
||||
assert.Equal(t, usr.Email, usr2.Email)
|
||||
|
||||
// Check groups are loaded
|
||||
assert.NotEqual(t, uuid.Nil, usr2.DefaultGroupID)
|
||||
assert.NotEmpty(t, usr2.GroupIDs)
|
||||
assert.NotNil(t, usr2.GroupID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,26 +21,26 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
Description: "Garage location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
loc2, err := tRepos.Locations.Create(context.Background(), tGroup.ID, LocationCreate{
|
||||
Name: "Test Basement",
|
||||
Description: "Basement location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// 2. Create labels
|
||||
label1, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Electronics",
|
||||
Description: "Electronics label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
label2, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Tools",
|
||||
Description: "Tools label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// 3. Create items
|
||||
item1, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Laptop",
|
||||
@@ -49,7 +49,7 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label1.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
item2, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Drill",
|
||||
Description: "Power drill",
|
||||
@@ -57,7 +57,7 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label2.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
item3, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Monitor",
|
||||
Description: "Computer monitor",
|
||||
@@ -65,7 +65,7 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label1.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// 4. Create maintenance entries
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item1.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
@@ -74,7 +74,7 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
Cost: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item2.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Drill maintenance",
|
||||
@@ -82,7 +82,7 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
Cost: 5.00,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item3.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "Monitor calibration",
|
||||
@@ -90,47 +90,47 @@ func TestWipeInventory_Integration(t *testing.T) {
|
||||
Cost: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// 5. Verify items exist
|
||||
allItems, err := tRepos.Items.GetAll(context.Background(), tGroup.ID)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(allItems), 3, "Should have at least 3 items")
|
||||
|
||||
|
||||
// 6. Verify maintenance entries exist
|
||||
maint1List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, maint1List, "Item 1 should have maintenance records")
|
||||
|
||||
|
||||
maint2List, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item2.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, maint2List, "Item 2 should have maintenance records")
|
||||
|
||||
|
||||
// 7. Test wipe inventory with all options enabled
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, true, true, true)
|
||||
require.NoError(t, err)
|
||||
assert.Positive(t, deleted, "Should have deleted entities")
|
||||
|
||||
assert.Greater(t, deleted, 0, "Should have deleted entities")
|
||||
|
||||
// 8. Verify all items are deleted
|
||||
allItemsAfter, err := tRepos.Items.GetAll(context.Background(), tGroup.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, allItemsAfter, "All items should be deleted")
|
||||
|
||||
assert.Equal(t, 0, len(allItemsAfter), "All items should be deleted")
|
||||
|
||||
// 9. Verify maintenance entries are deleted
|
||||
maint1After, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item1.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maint1After, "Item 1 maintenance records should be deleted")
|
||||
|
||||
|
||||
// 10. Verify labels are deleted
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label1.ID)
|
||||
require.Error(t, err, "Label 1 should be deleted")
|
||||
|
||||
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label2.ID)
|
||||
require.Error(t, err, "Label 2 should be deleted")
|
||||
|
||||
|
||||
// 11. Verify locations are deleted
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc1.ID)
|
||||
require.Error(t, err, "Location 1 should be deleted")
|
||||
|
||||
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc2.ID)
|
||||
require.Error(t, err, "Location 2 should be deleted")
|
||||
}
|
||||
@@ -143,13 +143,13 @@ func TestWipeInventory_SelectiveWipe(t *testing.T) {
|
||||
Description: "Office location",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
label, err := tRepos.Labels.Create(context.Background(), tGroup.ID, LabelCreate{
|
||||
Name: "Test Important",
|
||||
Description: "Important label",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
item, err := tRepos.Items.Create(context.Background(), tGroup.ID, ItemCreate{
|
||||
Name: "Test Computer",
|
||||
Description: "Desktop computer",
|
||||
@@ -157,7 +157,7 @@ func TestWipeInventory_SelectiveWipe(t *testing.T) {
|
||||
LabelIDs: []uuid.UUID{label.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
_, err = tRepos.MaintEntry.Create(context.Background(), item.ID, MaintenanceEntryCreate{
|
||||
CompletedDate: types.DateFromTime(time.Now()),
|
||||
Name: "System update",
|
||||
@@ -165,29 +165,29 @@ func TestWipeInventory_SelectiveWipe(t *testing.T) {
|
||||
Cost: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Test: Wipe only items (keep labels and locations)
|
||||
deleted, err := tRepos.Items.WipeInventory(context.Background(), tGroup.ID, false, false, false)
|
||||
require.NoError(t, err)
|
||||
assert.Positive(t, deleted, "Should have deleted at least items")
|
||||
|
||||
assert.Greater(t, deleted, 0, "Should have deleted at least items")
|
||||
|
||||
// Verify item is deleted
|
||||
_, err = tRepos.Items.GetOneByGroup(context.Background(), tGroup.ID, item.ID)
|
||||
require.Error(t, err, "Item should be deleted")
|
||||
|
||||
|
||||
// Verify maintenance is cascade deleted
|
||||
maintList, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, maintList, "Maintenance should be cascade deleted")
|
||||
|
||||
|
||||
// Verify label still exists
|
||||
_, err = tRepos.Labels.GetOneByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
require.NoError(t, err, "Label should still exist")
|
||||
|
||||
|
||||
// Verify location still exists
|
||||
_, err = tRepos.Locations.Get(context.Background(), loc.ID)
|
||||
require.NoError(t, err, "Location should still exist")
|
||||
|
||||
|
||||
// Cleanup
|
||||
_ = tRepos.Labels.DeleteByGroup(context.Background(), tGroup.ID, label.ID)
|
||||
_ = tRepos.Locations.delete(context.Background(), loc.ID)
|
||||
|
||||
@@ -65,14 +65,14 @@ type WebConfig struct {
|
||||
}
|
||||
|
||||
type LabelMakerConf struct {
|
||||
Width int64 `yaml:"width" conf:"default:526"`
|
||||
Height int64 `yaml:"height" conf:"default:200"`
|
||||
Padding int64 `yaml:"padding" conf:"default:32"`
|
||||
Margin int64 `yaml:"margin" conf:"default:32"`
|
||||
FontSize float64 `yaml:"font_size" conf:"default:32.0"`
|
||||
Width int64 `yaml:"width" conf:"default:526"`
|
||||
Height int64 `yaml:"height" conf:"default:200"`
|
||||
Padding int64 `yaml:"padding" conf:"default:32"`
|
||||
Margin int64 `yaml:"margin" conf:"default:32"`
|
||||
FontSize float64 `yaml:"font_size" conf:"default:32.0"`
|
||||
PrintCommand *string `yaml:"string"`
|
||||
AdditionalInformation *string `yaml:"string"`
|
||||
DynamicLength bool `yaml:"bool" conf:"default:true"`
|
||||
DynamicLength bool `yaml:"bool" conf:"default:true"`
|
||||
LabelServiceUrl *string `yaml:"label_service_url"`
|
||||
LabelServiceTimeout *time.Duration `yaml:"label_service_timeout"`
|
||||
RegularFontPath *string `yaml:"regular_font_path"`
|
||||
@@ -80,13 +80,13 @@ type LabelMakerConf struct {
|
||||
}
|
||||
|
||||
type OIDCConf struct {
|
||||
Enabled bool `yaml:"enabled" conf:"default:false"`
|
||||
Enabled bool `yaml:"enabled" conf:"default:false"`
|
||||
IssuerURL string `yaml:"issuer_url"`
|
||||
ClientID string `yaml:"client_id"`
|
||||
ClientSecret string `yaml:"client_secret"`
|
||||
Scope string `yaml:"scope" conf:"default:openid profile email"`
|
||||
AllowedGroups string `yaml:"allowed_groups"`
|
||||
AutoRedirect bool `yaml:"auto_redirect" conf:"default:false"`
|
||||
AutoRedirect bool `yaml:"auto_redirect" conf:"default:false"`
|
||||
VerifyEmail bool `yaml:"verify_email" conf:"default:false"`
|
||||
GroupClaim string `yaml:"group_claim" conf:"default:groups"`
|
||||
EmailClaim string `yaml:"email_claim" conf:"default:email"`
|
||||
|
||||
@@ -22,13 +22,13 @@ func RemoveAccents(text string) string {
|
||||
// 2. Removes diacritical marks (combining characters)
|
||||
// 3. Normalizes back to NFC (canonical composition)
|
||||
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||
|
||||
|
||||
result, _, err := transform.String(t, text)
|
||||
if err != nil {
|
||||
// If transformation fails, return the original text
|
||||
return text
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -241,15 +241,12 @@
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Groups",
|
||||
"summary": "Get Group",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,31 +286,6 @@
|
||||
}
|
||||
},
|
||||
"/v1/groups/invitations": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Invitations",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.GroupInvitation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -464,147 +436,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Create Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Group Name",
|
||||
"name": "name",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Delete Group",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Members",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.UserOut"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Add User to Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User ID",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.GroupMemberAdd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members/{user_id}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Remove User from Group",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID",
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/items": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3696,10 +3527,6 @@
|
||||
"description": "CreatedAt holds the value of the \"created_at\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_group_id": {
|
||||
"description": "DefaultGroupID holds the value of the \"default_group_id\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
|
||||
"allOf": [
|
||||
@@ -3760,12 +3587,13 @@
|
||||
"$ref": "#/definitions/ent.AuthTokens"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups holds the value of the groups edge.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
"group": {
|
||||
"description": "Group holds the value of the group edge.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifiers": {
|
||||
"description": "Notifiers holds the value of the notifiers edge.",
|
||||
@@ -3859,23 +3687,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupStatistics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5093,17 +4904,14 @@
|
||||
"repo.UserOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultGroupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
@@ -5324,17 +5132,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupMemberAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ItemAttachmentToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -719,9 +719,6 @@ definitions:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
default_group_id:
|
||||
description: DefaultGroupID holds the value of the "default_group_id" field.
|
||||
type: string
|
||||
edges:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.UserEdges'
|
||||
@@ -764,11 +761,10 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/ent.AuthTokens'
|
||||
type: array
|
||||
groups:
|
||||
description: Groups holds the value of the groups edge.
|
||||
items:
|
||||
$ref: '#/definitions/ent.Group'
|
||||
type: array
|
||||
group:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.Group'
|
||||
description: Group holds the value of the group edge.
|
||||
notifiers:
|
||||
description: Notifiers holds the value of the notifiers edge.
|
||||
items:
|
||||
@@ -832,17 +828,6 @@ definitions:
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
repo.GroupInvitation:
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
group:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
id:
|
||||
type: string
|
||||
uses:
|
||||
type: integer
|
||||
type: object
|
||||
repo.GroupStatistics:
|
||||
properties:
|
||||
totalItemPrice:
|
||||
@@ -1675,14 +1660,12 @@ definitions:
|
||||
type: object
|
||||
repo.UserOut:
|
||||
properties:
|
||||
defaultGroupId:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
groupIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
groupId:
|
||||
type: string
|
||||
groupName:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isOwner:
|
||||
@@ -1827,13 +1810,6 @@ definitions:
|
||||
required:
|
||||
- uses
|
||||
type: object
|
||||
v1.GroupMemberAdd:
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
required:
|
||||
- userId
|
||||
type: object
|
||||
v1.ItemAttachmentToken:
|
||||
properties:
|
||||
token:
|
||||
@@ -2056,12 +2032,10 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
type: array
|
||||
$ref: '#/definitions/repo.Group'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Groups
|
||||
summary: Get Group
|
||||
tags:
|
||||
- Group
|
||||
put:
|
||||
@@ -2084,106 +2058,7 @@ paths:
|
||||
summary: Update Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}:
|
||||
delete:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Delete Group
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: Group Name
|
||||
in: body
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}/members:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.UserOut'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Group Members
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.GroupMemberAdd'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Add User to Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}/members/{user_id}:
|
||||
delete:
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: user_id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Remove User from Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/invitations:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.GroupInvitation'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Group Invitations
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: User Data
|
||||
|
||||
@@ -242,17 +242,14 @@
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Groups",
|
||||
"summary": "Get Group",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
}
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,32 +292,6 @@
|
||||
}
|
||||
},
|
||||
"/v1/groups/invitations": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Invitations",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/repo.GroupInvitation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -480,142 +451,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Create Group",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Group Name",
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Delete Group",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Members",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/repo.UserOut"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Add User to Group",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/v1.GroupMemberAdd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "User ID",
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members/{user_id}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Remove User from Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User ID",
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/items": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3892,10 +3727,6 @@
|
||||
"description": "CreatedAt holds the value of the \"created_at\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_group_id": {
|
||||
"description": "DefaultGroupID holds the value of the \"default_group_id\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
|
||||
"allOf": [
|
||||
@@ -3956,12 +3787,13 @@
|
||||
"$ref": "#/components/schemas/ent.AuthTokens"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups holds the value of the groups edge.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ent.Group"
|
||||
}
|
||||
"group": {
|
||||
"description": "Group holds the value of the group edge.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ent.Group"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifiers": {
|
||||
"description": "Notifiers holds the value of the notifiers edge.",
|
||||
@@ -4055,23 +3887,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"$ref": "#/components/schemas/repo.Group"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupStatistics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5289,17 +5104,14 @@
|
||||
"repo.UserOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultGroupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
@@ -5520,17 +5332,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupMemberAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ItemAttachmentToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -142,16 +142,14 @@ paths:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Get All Groups
|
||||
summary: Get Group
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
put:
|
||||
security:
|
||||
- Bearer: []
|
||||
@@ -173,21 +171,6 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
/v1/groups/invitations:
|
||||
get:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Get All Group Invitations
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/repo.GroupInvitation"
|
||||
post:
|
||||
security:
|
||||
- Bearer: []
|
||||
@@ -279,85 +262,6 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/repo.ValueOverTime"
|
||||
"/v1/groups/{id}":
|
||||
post:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Create Group
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: Group Name
|
||||
required: true
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
delete:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Delete Group
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"/v1/groups/{id}/members":
|
||||
get:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Get All Group Members
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/repo.UserOut"
|
||||
post:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Add User to Group
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/v1.GroupMemberAdd"
|
||||
description: User ID
|
||||
required: true
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"/v1/groups/{id}/members/{user_id}":
|
||||
delete:
|
||||
security:
|
||||
- Bearer: []
|
||||
tags:
|
||||
- Group
|
||||
summary: Remove User from Group
|
||||
parameters:
|
||||
- description: User ID
|
||||
name: user_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
/v1/items:
|
||||
get:
|
||||
security:
|
||||
@@ -2418,9 +2322,6 @@ components:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
default_group_id:
|
||||
description: DefaultGroupID holds the value of the "default_group_id" field.
|
||||
type: string
|
||||
edges:
|
||||
description: >-
|
||||
Edges holds the relations/edges for other nodes in the graph.
|
||||
@@ -2464,11 +2365,10 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ent.AuthTokens"
|
||||
groups:
|
||||
description: Groups holds the value of the groups edge.
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ent.Group"
|
||||
group:
|
||||
description: Group holds the value of the group edge.
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ent.Group"
|
||||
notifiers:
|
||||
description: Notifiers holds the value of the notifiers edge.
|
||||
type: array
|
||||
@@ -2531,17 +2431,6 @@ components:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
repo.GroupInvitation:
|
||||
type: object
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
group:
|
||||
$ref: "#/components/schemas/repo.Group"
|
||||
id:
|
||||
type: string
|
||||
uses:
|
||||
type: integer
|
||||
repo.GroupStatistics:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3375,14 +3264,12 @@ components:
|
||||
repo.UserOut:
|
||||
type: object
|
||||
properties:
|
||||
defaultGroupId:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
groupIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
groupId:
|
||||
type: string
|
||||
groupName:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isOwner:
|
||||
@@ -3526,13 +3413,6 @@ components:
|
||||
type: integer
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
v1.GroupMemberAdd:
|
||||
type: object
|
||||
required:
|
||||
- userId
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
v1.ItemAttachmentToken:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -241,15 +241,12 @@
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Groups",
|
||||
"summary": "Get Group",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,31 +286,6 @@
|
||||
}
|
||||
},
|
||||
"/v1/groups/invitations": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Invitations",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.GroupInvitation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@@ -464,147 +436,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Create Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Group Name",
|
||||
"name": "name",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Delete Group",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get All Group Members",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo.UserOut"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Add User to Group",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User ID",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.GroupMemberAdd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups/{id}/members/{user_id}": {
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Remove User from Group",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "User ID",
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/items": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3696,10 +3527,6 @@
|
||||
"description": "CreatedAt holds the value of the \"created_at\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_group_id": {
|
||||
"description": "DefaultGroupID holds the value of the \"default_group_id\" field.",
|
||||
"type": "string"
|
||||
},
|
||||
"edges": {
|
||||
"description": "Edges holds the relations/edges for other nodes in the graph.\nThe values are being populated by the UserQuery when eager-loading is set.",
|
||||
"allOf": [
|
||||
@@ -3760,12 +3587,13 @@
|
||||
"$ref": "#/definitions/ent.AuthTokens"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups holds the value of the groups edge.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
"group": {
|
||||
"description": "Group holds the value of the group edge.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ent.Group"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifiers": {
|
||||
"description": "Notifiers holds the value of the notifiers edge.",
|
||||
@@ -3859,23 +3687,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiresAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"$ref": "#/definitions/repo.Group"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.GroupStatistics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5093,17 +4904,14 @@
|
||||
"repo.UserOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultGroupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
@@ -5324,17 +5132,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupMemberAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ItemAttachmentToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -719,9 +719,6 @@ definitions:
|
||||
created_at:
|
||||
description: CreatedAt holds the value of the "created_at" field.
|
||||
type: string
|
||||
default_group_id:
|
||||
description: DefaultGroupID holds the value of the "default_group_id" field.
|
||||
type: string
|
||||
edges:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.UserEdges'
|
||||
@@ -764,11 +761,10 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/ent.AuthTokens'
|
||||
type: array
|
||||
groups:
|
||||
description: Groups holds the value of the groups edge.
|
||||
items:
|
||||
$ref: '#/definitions/ent.Group'
|
||||
type: array
|
||||
group:
|
||||
allOf:
|
||||
- $ref: '#/definitions/ent.Group'
|
||||
description: Group holds the value of the group edge.
|
||||
notifiers:
|
||||
description: Notifiers holds the value of the notifiers edge.
|
||||
items:
|
||||
@@ -832,17 +828,6 @@ definitions:
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
repo.GroupInvitation:
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
group:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
id:
|
||||
type: string
|
||||
uses:
|
||||
type: integer
|
||||
type: object
|
||||
repo.GroupStatistics:
|
||||
properties:
|
||||
totalItemPrice:
|
||||
@@ -1675,14 +1660,12 @@ definitions:
|
||||
type: object
|
||||
repo.UserOut:
|
||||
properties:
|
||||
defaultGroupId:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
groupIds:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
groupId:
|
||||
type: string
|
||||
groupName:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isOwner:
|
||||
@@ -1827,13 +1810,6 @@ definitions:
|
||||
required:
|
||||
- uses
|
||||
type: object
|
||||
v1.GroupMemberAdd:
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
required:
|
||||
- userId
|
||||
type: object
|
||||
v1.ItemAttachmentToken:
|
||||
properties:
|
||||
token:
|
||||
@@ -2056,12 +2032,10 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
type: array
|
||||
$ref: '#/definitions/repo.Group'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Groups
|
||||
summary: Get Group
|
||||
tags:
|
||||
- Group
|
||||
put:
|
||||
@@ -2084,106 +2058,7 @@ paths:
|
||||
summary: Update Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}:
|
||||
delete:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Delete Group
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: Group Name
|
||||
in: body
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/repo.Group'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}/members:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.UserOut'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Group Members
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.GroupMemberAdd'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Add User to Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/{id}/members/{user_id}:
|
||||
delete:
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: user_id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Remove User from Group
|
||||
tags:
|
||||
- Group
|
||||
/v1/groups/invitations:
|
||||
get:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/repo.GroupInvitation'
|
||||
type: array
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get All Group Invitations
|
||||
tags:
|
||||
- Group
|
||||
post:
|
||||
parameters:
|
||||
- description: User Data
|
||||
|
||||
@@ -7,16 +7,12 @@ describe("first time user workflow (register, login, join group)", () => {
|
||||
test("user should be able to update group", async () => {
|
||||
const { client } = await factories.client.singleUse();
|
||||
|
||||
const { data: user } = await client.user.self();
|
||||
const name = faker.person.firstName();
|
||||
|
||||
const { response, data: group } = await client.group.update(
|
||||
{
|
||||
name,
|
||||
currency: "eur",
|
||||
},
|
||||
user.item.defaultGroupId
|
||||
);
|
||||
const { response, data: group } = await client.group.update({
|
||||
name,
|
||||
currency: "eur",
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(group.name).toBe(name);
|
||||
@@ -25,8 +21,7 @@ describe("first time user workflow (register, login, join group)", () => {
|
||||
test("user should be able to get own group", async () => {
|
||||
const { client } = await factories.client.singleUse();
|
||||
|
||||
const { data: user } = await client.user.self();
|
||||
const { response, data: group } = await client.group.get(user.item.defaultGroupId);
|
||||
const { response, data: group } = await client.group.get();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(group.name).toBeTruthy();
|
||||
@@ -62,7 +57,7 @@ describe("first time user workflow (register, login, join group)", () => {
|
||||
const client2 = factories.client.user(loginData.token);
|
||||
const { data: user2 } = await client2.user.self();
|
||||
|
||||
expect(user2.item.defaultGroupId).toBe(user1.item.defaultGroupId);
|
||||
user2.item.groupName = user1.item.groupName;
|
||||
|
||||
// Cleanup User 2
|
||||
const { response: deleteResp } = await client2.user.delete();
|
||||
|
||||
@@ -15,16 +15,16 @@ export class GroupApi extends BaseAPI {
|
||||
});
|
||||
}
|
||||
|
||||
update(data: GroupUpdate, groupId?: string) {
|
||||
update(data: GroupUpdate) {
|
||||
return this.http.put<GroupUpdate, Group>({
|
||||
url: route(`/groups/${groupId || ""}`),
|
||||
url: route("/groups"),
|
||||
body: data,
|
||||
});
|
||||
}
|
||||
|
||||
get(groupId?: string) {
|
||||
get() {
|
||||
return this.http.get<Group>({
|
||||
url: route(`/groups/${groupId || ""}`),
|
||||
url: route("/groups"),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
21
frontend/lib/api/types/data-contracts.ts
generated
21
frontend/lib/api/types/data-contracts.ts
generated
@@ -508,8 +508,6 @@ export interface EntUser {
|
||||
activated_on: string;
|
||||
/** CreatedAt holds the value of the "created_at" field. */
|
||||
created_at: string;
|
||||
/** DefaultGroupID holds the value of the "default_group_id" field. */
|
||||
default_group_id: string;
|
||||
/**
|
||||
* Edges holds the relations/edges for other nodes in the graph.
|
||||
* The values are being populated by the UserQuery when eager-loading is set.
|
||||
@@ -538,8 +536,8 @@ export interface EntUser {
|
||||
export interface EntUserEdges {
|
||||
/** AuthTokens holds the value of the auth_tokens edge. */
|
||||
auth_tokens: EntAuthTokens[];
|
||||
/** Groups holds the value of the groups edge. */
|
||||
groups: EntGroup[];
|
||||
/** Group holds the value of the group edge. */
|
||||
group: EntGroup;
|
||||
/** Notifiers holds the value of the notifiers edge. */
|
||||
notifiers: EntNotifier[];
|
||||
}
|
||||
@@ -572,13 +570,6 @@ export interface Group {
|
||||
updatedAt: Date | string;
|
||||
}
|
||||
|
||||
export interface GroupInvitation {
|
||||
expiresAt: Date | string;
|
||||
group: Group;
|
||||
id: string;
|
||||
uses: number;
|
||||
}
|
||||
|
||||
export interface GroupStatistics {
|
||||
totalItemPrice: number;
|
||||
totalItems: number;
|
||||
@@ -1036,9 +1027,9 @@ export interface TreeItem {
|
||||
}
|
||||
|
||||
export interface UserOut {
|
||||
defaultGroupId: string;
|
||||
email: string;
|
||||
groupIds: string[];
|
||||
groupId: string;
|
||||
groupName: string;
|
||||
id: string;
|
||||
isOwner: boolean;
|
||||
isSuperuser: boolean;
|
||||
@@ -1121,10 +1112,6 @@ export interface GroupInvitationCreate {
|
||||
uses: number;
|
||||
}
|
||||
|
||||
export interface GroupMemberAdd {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface ItemAttachmentToken {
|
||||
token: string;
|
||||
}
|
||||
|
||||
@@ -521,42 +521,45 @@
|
||||
"update_label": "Update Label"
|
||||
},
|
||||
"languages": {
|
||||
"bs-BA": "Bosnian (Bosnia and Herzegovina)",
|
||||
"ca": "Catalan",
|
||||
"cs-CZ": "Czech",
|
||||
"da-DK": "Danish",
|
||||
"de": "German",
|
||||
"ar-AA": "Arabic (العربية)",
|
||||
"bs-BA": "Bosnian (bosanski)",
|
||||
"ca": "Catalan (català)",
|
||||
"cs-CZ": "Czech (čeština)",
|
||||
"da-DK": "Danish (dansk)",
|
||||
"de": "German (Deutsch)",
|
||||
"el-GR": "Greek (Ελληνικά)",
|
||||
"en": "English",
|
||||
"es": "Spanish",
|
||||
"fi-FI": "Finnish",
|
||||
"fr": "French",
|
||||
"hu": "Hungarian",
|
||||
"id-ID": "Indonesian",
|
||||
"it": "Italian",
|
||||
"ja-JP": "Japanese",
|
||||
"ko-KR": "Korean",
|
||||
"lb-LU": "Luxembourgish (Luxembourg)",
|
||||
"lt-LT": "Lithuanian (Lithuania)",
|
||||
"nb-NO": "Norwegian Bokmål",
|
||||
"nl": "Dutch",
|
||||
"pl": "Polish",
|
||||
"pt-BR": "Portuguese (Brazil)",
|
||||
"pt-PT": "Portuguese (Portugal)",
|
||||
"ro-RO": "Romanian",
|
||||
"ru": "Russian",
|
||||
"sk-SK": "Slovak",
|
||||
"sl": "Slovenian",
|
||||
"sq-AL": "Albanian",
|
||||
"sv": "Swedish",
|
||||
"ta-IN": "Tamil",
|
||||
"th-TH": "Thai",
|
||||
"tr": "Turkish",
|
||||
"uk-UA": "Ukrainian",
|
||||
"vi-VN": "Vietnamese",
|
||||
"zh-CN": "Chinese (Simplified)",
|
||||
"zh-HK": "Chinese (Hong Kong)",
|
||||
"zh-MO": "Chinese (Macau)",
|
||||
"zh-TW": "Chinese (Traditional)"
|
||||
"es": "Spanish (español)",
|
||||
"fi-FI": "Finnish (suomi)",
|
||||
"fr": "French (français)",
|
||||
"hu": "Hungarian (magyar)",
|
||||
"id-ID": "Indonesian (Indonesia)",
|
||||
"it": "Italian (italiano)",
|
||||
"ja-JP": "Japanese (日本語)",
|
||||
"ko-KR": "Korean (한국어)",
|
||||
"lb-LU": "Luxembourgish (Lëtzebuergesch)",
|
||||
"lt-LT": "Lithuanian (lietuvių)",
|
||||
"nb-NO": "Norwegian Bokmål (norsk bokmål)",
|
||||
"nl": "Dutch (Nederlands)",
|
||||
"pl": "Polish (polski)",
|
||||
"pt-BR": "Portuguese - Brazil (português)",
|
||||
"pt-PT": "Portuguese - Portugal (português)",
|
||||
"ro-RO": "Romanian (română)",
|
||||
"ru": "Russian (русский)",
|
||||
"sk-SK": "Slovak (slovenčina)",
|
||||
"sl": "Slovenian (slovenščina)",
|
||||
"sq-AL": "Albanian (shqip)",
|
||||
"sv": "Swedish (svenska)",
|
||||
"ta-IN": "Tamil (தமிழ்)",
|
||||
"te-IN": "Telugu (తెలుగు)",
|
||||
"th-TH": "Thai (ไทย)",
|
||||
"tr": "Turkish (Türkçe)",
|
||||
"uk-UA": "Ukrainian (українська)",
|
||||
"vi-VN": "Vietnamese (Tiếng Việt)",
|
||||
"zh-CN": "Chinese - Simplified (中文)",
|
||||
"zh-HK": "Chinese - Hong Kong (中文)",
|
||||
"zh-MO": "Chinese - Macau (中文)",
|
||||
"zh-TW": "Chinese - Traditional (中文)"
|
||||
},
|
||||
"locations": {
|
||||
"child_locations": "Child Locations",
|
||||
|
||||
@@ -1,182 +1,129 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const STATUS_ROUTE = "**/api/v1/status";
|
||||
const WIPE_ROUTE = "**/api/v1/actions/wipe-inventory";
|
||||
|
||||
const buildStatusResponse = (demo: boolean) => ({
|
||||
allowRegistration: true,
|
||||
build: { buildTime: new Date().toISOString(), commit: "test", version: "v0.0.0" },
|
||||
demo,
|
||||
health: true,
|
||||
labelPrinting: false,
|
||||
latest: { date: new Date().toISOString(), version: "v0.0.0" },
|
||||
message: "",
|
||||
oidc: { allowLocal: true, autoRedirect: false, buttonText: "", enabled: false },
|
||||
title: "Homebox",
|
||||
versions: [],
|
||||
});
|
||||
|
||||
async function mockStatus(page: Page, demo: boolean) {
|
||||
await page.route(STATUS_ROUTE, route => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(buildStatusResponse(demo)),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function login(page: Page, email = "demo@example.com", password = "demo") {
|
||||
await page.goto("/home");
|
||||
await expect(page).toHaveURL("/");
|
||||
await page.fill("input[type='text']", email);
|
||||
await page.fill("input[type='password']", password);
|
||||
await page.click("button[type='submit']");
|
||||
await expect(page).toHaveURL("/home");
|
||||
}
|
||||
|
||||
async function openWipeInventory(page: Page) {
|
||||
await page.goto("/tools");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
|
||||
const wipeButton = page.getByRole("button", { name: "Wipe Inventory" }).last();
|
||||
await expect(wipeButton).toBeVisible();
|
||||
await wipeButton.click();
|
||||
}
|
||||
|
||||
test.describe("Wipe Inventory", () => {
|
||||
test("shows demo mode warning without wipe options", async ({ page }) => {
|
||||
await mockStatus(page, true);
|
||||
await login(page);
|
||||
await openWipeInventory(page);
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
"Inventory, labels, locations and maintenance records cannot be wiped whilst Homebox is in demo mode.",
|
||||
{ exact: false }
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.locator("input#wipe-labels-checkbox")).toHaveCount(0);
|
||||
await expect(page.locator("input#wipe-locations-checkbox")).toHaveCount(0);
|
||||
await expect(page.locator("input#wipe-maintenance-checkbox")).toHaveCount(0);
|
||||
test.describe("Wipe Inventory E2E Test", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as demo user (owner with permissions)
|
||||
await page.goto("/");
|
||||
await page.fill("input[type='text']", "demo@example.com");
|
||||
await page.fill("input[type='password']", "demo");
|
||||
await page.click("button[type='submit']");
|
||||
await expect(page).toHaveURL("/home");
|
||||
});
|
||||
|
||||
test.describe("production mode", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await mockStatus(page, false);
|
||||
await login(page);
|
||||
test("should open wipe inventory dialog with all options", async ({ page }) => {
|
||||
// Navigate to Tools page
|
||||
await page.goto("/tools");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Scroll to the bottom where wipe inventory is located
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Find and click the Wipe Inventory button
|
||||
const wipeButton = page.locator("button", { hasText: "Wipe Inventory" }).last();
|
||||
await expect(wipeButton).toBeVisible();
|
||||
await wipeButton.click();
|
||||
|
||||
// Wait for dialog to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify dialog title is visible
|
||||
await expect(page.locator("text=Wipe Inventory").first()).toBeVisible();
|
||||
|
||||
// Verify all checkboxes are present
|
||||
await expect(page.locator("input#wipe-labels-checkbox")).toBeVisible();
|
||||
await expect(page.locator("input#wipe-locations-checkbox")).toBeVisible();
|
||||
await expect(page.locator("input#wipe-maintenance-checkbox")).toBeVisible();
|
||||
|
||||
// Verify labels for checkboxes
|
||||
await expect(page.locator("label[for='wipe-labels-checkbox']")).toBeVisible();
|
||||
await expect(page.locator("label[for='wipe-locations-checkbox']")).toBeVisible();
|
||||
await expect(page.locator("label[for='wipe-maintenance-checkbox']")).toBeVisible();
|
||||
|
||||
// Verify both Cancel and Confirm buttons are present
|
||||
await expect(page.locator("button", { hasText: "Cancel" })).toBeVisible();
|
||||
const confirmButton = page.locator("button", { hasText: "Confirm" });
|
||||
await expect(confirmButton).toBeVisible();
|
||||
|
||||
// Take screenshot of the modal
|
||||
await page.screenshot({
|
||||
path: "/tmp/playwright-logs/wipe-inventory-modal-initial.png",
|
||||
});
|
||||
console.log("✅ Screenshot saved: wipe-inventory-modal-initial.png");
|
||||
|
||||
test("renders wipe options and submits all flags", async ({ page }) => {
|
||||
await page.route(WIPE_ROUTE, route => {
|
||||
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ completed: 0 }) });
|
||||
});
|
||||
// Check all three options
|
||||
await page.check("input#wipe-labels-checkbox");
|
||||
await page.check("input#wipe-locations-checkbox");
|
||||
await page.check("input#wipe-maintenance-checkbox");
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await openWipeInventory(page);
|
||||
await expect(page.getByText("Wipe Inventory").first()).toBeVisible();
|
||||
// Verify checkboxes are checked
|
||||
await expect(page.locator("input#wipe-labels-checkbox")).toBeChecked();
|
||||
await expect(page.locator("input#wipe-locations-checkbox")).toBeChecked();
|
||||
await expect(page.locator("input#wipe-maintenance-checkbox")).toBeChecked();
|
||||
|
||||
const labels = page.locator("input#wipe-labels-checkbox");
|
||||
const locations = page.locator("input#wipe-locations-checkbox");
|
||||
const maintenance = page.locator("input#wipe-maintenance-checkbox");
|
||||
|
||||
await expect(labels).toBeVisible();
|
||||
await expect(locations).toBeVisible();
|
||||
await expect(maintenance).toBeVisible();
|
||||
|
||||
await labels.check();
|
||||
await locations.check();
|
||||
await maintenance.check();
|
||||
|
||||
const requestPromise = page.waitForRequest(WIPE_ROUTE);
|
||||
await page.getByRole("button", { name: "Confirm" }).last().click();
|
||||
const request = await requestPromise;
|
||||
|
||||
expect(request.postDataJSON()).toEqual({
|
||||
wipeLabels: true,
|
||||
wipeLocations: true,
|
||||
wipeMaintenance: true,
|
||||
});
|
||||
|
||||
await expect(page.locator("[role='status']").first()).toBeVisible();
|
||||
// Take screenshot with all options checked
|
||||
await page.screenshot({
|
||||
path: "/tmp/playwright-logs/wipe-inventory-modal-options-checked.png",
|
||||
});
|
||||
console.log("✅ Screenshot saved: wipe-inventory-modal-options-checked.png");
|
||||
|
||||
test("blocks wipe attempts from non-owners", async ({ page }) => {
|
||||
await page.route(WIPE_ROUTE, route => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ message: "forbidden" }),
|
||||
});
|
||||
});
|
||||
// Click Confirm button
|
||||
await confirmButton.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await openWipeInventory(page);
|
||||
// Wait for the dialog to close (verify button is no longer visible)
|
||||
await expect(confirmButton).not.toBeVisible({ timeout: 5000 });
|
||||
|
||||
const requestPromise = page.waitForRequest(WIPE_ROUTE);
|
||||
await page.getByRole("button", { name: "Confirm" }).last().click();
|
||||
await requestPromise;
|
||||
// Check for success toast notification
|
||||
// The toast should contain text about items being deleted
|
||||
const toastLocator = page.locator("[role='status'], [class*='toast'], [class*='sonner']");
|
||||
await expect(toastLocator.first()).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await expect(page.getByText("Failed to wipe inventory.")).toBeVisible();
|
||||
// Take screenshot of the page after confirmation
|
||||
await page.screenshot({
|
||||
path: "/tmp/playwright-logs/after-wipe-confirmation.png",
|
||||
fullPage: true,
|
||||
});
|
||||
console.log("✅ Screenshot saved: after-wipe-confirmation.png");
|
||||
|
||||
const checkboxCases = [
|
||||
{
|
||||
name: "labels only",
|
||||
selection: { labels: true, locations: false, maintenance: false },
|
||||
},
|
||||
{
|
||||
name: "locations only",
|
||||
selection: { labels: false, locations: true, maintenance: false },
|
||||
},
|
||||
{
|
||||
name: "maintenance only",
|
||||
selection: { labels: false, locations: false, maintenance: true },
|
||||
},
|
||||
];
|
||||
console.log("✅ Test completed successfully!");
|
||||
console.log("✅ Wipe Inventory dialog opened correctly");
|
||||
console.log("✅ All three options (labels, locations, maintenance) are available");
|
||||
console.log("✅ Confirm button triggers the action");
|
||||
console.log("✅ Dialog closes after confirmation");
|
||||
});
|
||||
|
||||
for (const scenario of checkboxCases) {
|
||||
test(`submits correct flags when ${scenario.name} is selected`, async ({ page }) => {
|
||||
await page.route(WIPE_ROUTE, route => {
|
||||
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ completed: 0 }) });
|
||||
});
|
||||
test("should cancel wipe inventory operation", async ({ page }) => {
|
||||
// Navigate to Tools page
|
||||
await page.goto("/tools");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await openWipeInventory(page);
|
||||
await expect(page.getByText("Wipe Inventory").first()).toBeVisible();
|
||||
// Scroll to wipe inventory section
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const labels = page.locator("input#wipe-labels-checkbox");
|
||||
const locations = page.locator("input#wipe-locations-checkbox");
|
||||
const maintenance = page.locator("input#wipe-maintenance-checkbox");
|
||||
// Click Wipe Inventory button
|
||||
const wipeButton = page.locator("button", { hasText: "Wipe Inventory" }).last();
|
||||
await wipeButton.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
if (scenario.selection.labels) {
|
||||
await labels.check();
|
||||
} else {
|
||||
await labels.uncheck();
|
||||
}
|
||||
// Verify dialog is open
|
||||
await expect(page.locator("text=Wipe Inventory").first()).toBeVisible();
|
||||
|
||||
if (scenario.selection.locations) {
|
||||
await locations.check();
|
||||
} else {
|
||||
await locations.uncheck();
|
||||
}
|
||||
// Click Cancel button
|
||||
const cancelButton = page.locator("button", { hasText: "Cancel" });
|
||||
await cancelButton.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
if (scenario.selection.maintenance) {
|
||||
await maintenance.check();
|
||||
} else {
|
||||
await maintenance.uncheck();
|
||||
}
|
||||
// Verify dialog is closed
|
||||
await expect(page.locator("text=Wipe Inventory").first()).not.toBeVisible({ timeout: 5000 });
|
||||
|
||||
const requestPromise = page.waitForRequest(WIPE_ROUTE);
|
||||
await page.getByRole("button", { name: "Confirm" }).last().click();
|
||||
const request = await requestPromise;
|
||||
|
||||
expect(request.postDataJSON()).toEqual({
|
||||
wipeLabels: scenario.selection.labels,
|
||||
wipeLocations: scenario.selection.locations,
|
||||
wipeMaintenance: scenario.selection.maintenance,
|
||||
});
|
||||
});
|
||||
}
|
||||
// Take screenshot after cancel
|
||||
await page.screenshot({
|
||||
path: "/tmp/playwright-logs/after-cancel.png",
|
||||
});
|
||||
console.log("✅ Screenshot saved: after-cancel.png");
|
||||
console.log("✅ Cancel button works correctly");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user