Kindergarten Activity Registration System Implementation with Spring Boot and Vue.js

Architecture Overview

The system employs a modern full-stack architecture with clear separation of concerns. The backend is built on Spring Boot, providing a robust RESTful API foundation. The frontend utilizes Vue.js for a responsive user interface, while UniApp enables cross-platform mobile functionality.

Backend Implementation

Authentication Service

@RestController
@RequestMapping("/api/auth")
public class AuthenticationController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @PostMapping("/authenticate")
    public ResponseEntity<JwtResponse> authenticateUser(@RequestBody LoginRequest loginRequest) {
        UserDetails userDetails = userService.authenticateUser(
            loginRequest.getUsername(), 
            loginRequest.getPassword()
        );
        
        String token = jwtTokenUtil.generateToken(userDetails);
        
        return ResponseEntity.ok(new JwtResponse(token));
    }
}

Activity Management

@Service
public class ActivityService {
    
    @Autowired
    private ActivityRepository activityRepository;
    
    public Page<Activity> getAvailableActivities(Pageable pageable) {
        return activityRepository.findByStatus(
            ActivityStatus.ACTIVE, 
            pageable
        );
    }
    
    @Transactional
    public EnrollmentResult enrollChild(EnrollmentRequest request) {
        Activity activity = activityRepository.findById(request.getActivityId())
            .orElseThrow(() -> new ActivityNotFoundException());
            
        if (activity.getAvailableSlots() <= 0) {
            return EnrollmentResult.noSlotsAvailable();
        }
        
        activity.decrementSlots();
        activityRepository.save(activity);
        
        return EnrollmentResult.success();
    }
}

Frontend Components

Activity List Component

<template>
  <div class="activity-container">
    <div v-for="activity in activities" 
         :key="activity.id" 
         class="activity-card">
      <h3>{{ activity.name }}</h3>
      <p>{{ activity.description }}</p>
      <div class="activity-meta">
        <span>Age: {{ activity.ageGroup }}</span>
        <span>Slots: {{ activity.availableSlots }}</span>
      </div>
      <button @click="enroll(activity.id)" 
              :disabled="activity.availableSlots === 0">
        Enroll Now
      </button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activities: []
    }
  },
  
  created() {
    this.fetchActivities();
  },
  
  methods: {
    async fetchActivities() {
      const response = await this.$http.get('/api/activities');
      this.activities = response.data.content;
    },
    
    async enroll(activityId) {
      try {
        await this.$http.post(`/api/enrollments`, {
          activityId: activityId,
          childId: this.selectedChild
        });
        
        this.$notify.success('Enrollment successful!');
        this.fetchActivities();
      } catch (error) {
        this.$notify.error('Enrollment failed');
      }
    }
  }
}
</script>

Database Schema

Activity Table

CREATE TABLE activities (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    age_group VARCHAR(20) NOT NULL,
    max_capacity INT NOT NULL,
    available_slots INT NOT NULL,
    schedule VARCHAR(50),
    fee DECIMAL(10,2),
    status ENUM('ACTIVE', 'FULL', 'CANCELLED') DEFAULT 'ACTIVE',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE enrollments (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    activity_id BIGINT NOT NULL,
    child_id BIGINT NOT NULL,
    parent_id BIGINT NOT NULL,
    enrollment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status ENUM('CONFIRMED', 'WAITLIST', 'CANCELLED') DEFAULT 'CONFIRMED',
    FOREIGN KEY (activity_id) REFERENCES activities(id),
    FOREIGN KEY (child_id) REFERENCES children(id),
    FOREIGN KEY (parent_id) REFERENCES parents(id)
);

Testing Strategies

API Testing

@SpringBootTest
@AutoConfigureTestDatabase
class EnrollmentControllerTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @MockBean
    private ActivityService activityService;
    
    @Test
    void shouldEnrollChildSuccessfully() {
        when(activityService.enrollChild(any(EnrollmentRequest.class)))
            .thenReturn(EnrollmentResult.success());
        
        ResponseEntity<String> response = restTemplate.postForEntity(
            "/api/enrollments", 
            new EnrollmentRequest(1L, 1L), 
            String.class
        );
        
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
    
    @Test
    void shouldRejectEnrollmentWhenFull() {
        when(activityService.enrollChild(any(EnrollmentRequest.class)))
            .thenReturn(EnrollmentResult.noSlotsAvailable());
        
        ResponseEntity<String> response = restTemplate.postForEntity(
            "/api/enrollments", 
            new EnrollmentRequest(1L, 1L), 
            String.class
        );
        
        assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
    }
}

Frontend Testing

import { mount } from '@vue/test-utils'
import ActivityList from '@/components/ActivityList.vue'

describe('ActivityList', () => {
  it('displays available activities', async () => {
    const mockActivities = [
      { id: 1, name: 'Art Class', availableSlots: 5 },
      { id: 2, name: 'Music Class', availableSlots: 0 }
    ]
    
    const wrapper = mount(ActivityList, {
      data() {
        return { activities: mockActivities }
      }
    })
    
    expect(wrapper.findAll('.activity-card')).toHaveLength(2)
    expect(wrapper.text()).toContain('Art Class')
    expect(wrapper.text()).toContain('Music Class')
  })
  
  it('disables enrollment button when full', async () => {
    const wrapper = mount(ActivityList, {
      data() {
        return {
          activities: [{ id: 1, name: 'Full Class', availableSlots: 0 }]
        }
      }
    })
    
    const button = wrapper.find('button')
    expect(button.attributes('disabled')).toBeDefined()
  })
})

Tags: Spring Boot Vue.js UniApp Educational Management Full Stack Development

Posted on Mon, 18 May 2026 06:18:59 +0000 by nealios